package services

import (
	"context"
	"fmt"
	"io"
	"mime/multipart"
	"os"
	"path/filepath"
	"time"

	"github.com/google/uuid"
	"go.uber.org/zap"
)

// TrackStorageService gère le stockage des fichiers audio
type TrackStorageService struct {
	localPath  string
	useS3      bool
	s3Service  interface{} // S3Service sera implémenté plus tard (T0224)
	logger     *zap.Logger
	maxRetries int
	retryDelay time.Duration
}

// S3Service interface pour le service S3 (à implémenter plus tard)
type S3Service interface {
	UploadFile(ctx context.Context, data []byte, key string, contentType string) (string, error)
	DeleteFile(ctx context.Context, key string) error
	GetPresignedURL(ctx context.Context, key string) (string, error)
}

// NewTrackStorageService crée un nouveau service de stockage de tracks
func NewTrackStorageService(localPath string, useS3 bool, logger *zap.Logger) *TrackStorageService {
	if localPath == "" {
		localPath = "uploads/tracks"
	}
	if logger == nil {
		logger = zap.NewNop()
	}
	return &TrackStorageService{
		localPath:  localPath,
		useS3:      useS3,
		logger:     logger,
		maxRetries: 3,
		retryDelay: time.Second * 2,
	}
}

// SetS3Service définit le service S3 (quand il sera disponible)
func (s *TrackStorageService) SetS3Service(s3Service S3Service) {
	s.s3Service = s3Service
	s.useS3 = s3Service != nil
}

// GetDownloadURL retourne une URL de téléchargement (signée pour S3, relative pour local)
func (s *TrackStorageService) GetDownloadURL(ctx context.Context, filePath string) (string, error) {
	if s.useS3 && s.s3Service != nil {
		s3Service, ok := s.s3Service.(S3Service)
		if !ok {
			return "", fmt.Errorf("invalid S3 service type")
		}
		// On suppose que filePath contient la clé ou l'URL complète.
		// Pour simplifier, on considère que filePath est la clé si on utilise S3.
		// En réalité, il faudrait extraire la clé de l'URL stockée si nécessaire.
		return s3Service.GetPresignedURL(ctx, filePath)
	}

	// Local storage: retourner le chemin tel quel (relatif)
	return filePath, nil
}

// SaveTrack sauvegarde un fichier audio avec structure tracks/{user_id}/{track_id}/{filename}
// MIGRATION UUID: userID migré vers uuid.UUID, trackID reste int64
func (s *TrackStorageService) SaveTrack(ctx context.Context, userID uuid.UUID, trackID int64, fileHeader *multipart.FileHeader) (string, error) {
	// Générer nom fichier unique
	ext := filepath.Ext(fileHeader.Filename)
	filename := fmt.Sprintf("%s%s", uuid.New().String(), ext)

	// Chemin: tracks/{user_id}/{trackID}/{filename}
	key := fmt.Sprintf("tracks/%s/%d/%s", userID.String(), trackID, filename)

	var filePath string
	var err error

	// Retry logic
	for attempt := 0; attempt < s.maxRetries; attempt++ {
		if attempt > 0 {
			s.logger.Warn("Retrying file upload",
				zap.Int("attempt", attempt+1),
				zap.String("user_id", userID.String()),
				zap.Int64("track_id", trackID),
			)
			time.Sleep(s.retryDelay * time.Duration(attempt))
		}

		if s.useS3 && s.s3Service != nil {
			filePath, err = s.saveToS3(ctx, fileHeader, key)
		} else {
			filePath, err = s.saveLocally(fileHeader, key)
		}

		if err == nil {
			s.logger.Info("Track file saved successfully",
				zap.String("path", filePath),
				zap.String("user_id", userID.String()),
				zap.Int64("track_id", trackID),
			)
			return filePath, nil
		}

		s.logger.Error("Failed to save track file",
			zap.Error(err),
			zap.Int("attempt", attempt+1),
			zap.String("user_id", userID.String()),
			zap.Int64("track_id", trackID),
		)
	}

	return "", fmt.Errorf("failed to save track file after %d attempts: %w", s.maxRetries, err)
}

// saveToS3 sauvegarde le fichier vers S3
func (s *TrackStorageService) saveToS3(ctx context.Context, fileHeader *multipart.FileHeader, key string) (string, error) {
	if s.s3Service == nil {
		return "", fmt.Errorf("S3 service not configured")
	}

	// Ouvrir le fichier
	file, err := fileHeader.Open()
	if err != nil {
		return "", fmt.Errorf("failed to open file: %w", err)
	}
	defer file.Close()

	// Lire le fichier en bytes
	fileBytes := make([]byte, fileHeader.Size)
	n, err := io.ReadFull(file, fileBytes)
	if err != nil && err != io.ErrUnexpectedEOF {
		return "", fmt.Errorf("failed to read file: %w", err)
	}
	fileBytes = fileBytes[:n]

	// Déterminer le Content-Type
	contentType := fileHeader.Header.Get("Content-Type")
	if contentType == "" {
		ext := filepath.Ext(fileHeader.Filename)
		contentType = s.getContentTypeFromExtension(ext)
	}

	// Upload vers S3
	s3Service, ok := s.s3Service.(S3Service)
	if !ok {
		return "", fmt.Errorf("invalid S3 service type")
	}

	url, err := s3Service.UploadFile(ctx, fileBytes, key, contentType)
	if err != nil {
		return "", fmt.Errorf("failed to upload to S3: %w", err)
	}

	return url, nil
}

// saveLocally sauvegarde le fichier localement
func (s *TrackStorageService) saveLocally(fileHeader *multipart.FileHeader, key string) (string, error) {
	// Chemin complet local
	destPath := filepath.Join(s.localPath, key)

	// Créer les répertoires nécessaires
	if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
		return "", fmt.Errorf("failed to create directory: %w", err)
	}

	// Ouvrir le fichier source
	file, err := fileHeader.Open()
	if err != nil {
		return "", fmt.Errorf("failed to open file: %w", err)
	}
	defer file.Close()

	// Créer le fichier de destination
	destFile, err := os.Create(destPath)
	if err != nil {
		return "", fmt.Errorf("failed to create file: %w", err)
	}
	defer destFile.Close()

	// Copier le contenu
	if _, err := io.Copy(destFile, file); err != nil {
		// Nettoyer en cas d'erreur
		os.Remove(destPath)
		return "", fmt.Errorf("failed to save file: %w", err)
	}

	// Retourner le chemin relatif pour l'URL
	relativePath := fmt.Sprintf("/uploads/%s", key)
	return relativePath, nil
}

// DeleteTrack supprime un fichier audio
func (s *TrackStorageService) DeleteTrack(ctx context.Context, userID, trackID int64, filename string) error {
	key := fmt.Sprintf("tracks/%d/%d/%s", userID, trackID, filename)

	if s.useS3 && s.s3Service != nil {
		return s.deleteFromS3(ctx, key)
	}

	return s.deleteLocally(key)
}

// deleteFromS3 supprime le fichier de S3
func (s *TrackStorageService) deleteFromS3(ctx context.Context, key string) error {
	if s.s3Service == nil {
		return fmt.Errorf("S3 service not configured")
	}

	s3Service, ok := s.s3Service.(S3Service)
	if !ok {
		return fmt.Errorf("invalid S3 service type")
	}

	if err := s3Service.DeleteFile(ctx, key); err != nil {
		return fmt.Errorf("failed to delete from S3: %w", err)
	}

	return nil
}

// deleteLocally supprime le fichier localement
func (s *TrackStorageService) deleteLocally(key string) error {
	destPath := filepath.Join(s.localPath, key)

	if err := os.Remove(destPath); err != nil {
		if !os.IsNotExist(err) {
			return fmt.Errorf("failed to delete file: %w", err)
		}
		// Le fichier n'existe pas, considérer comme succès
	}

	return nil
}

// getContentTypeFromExtension retourne le Content-Type basé sur l'extension
func (s *TrackStorageService) getContentTypeFromExtension(ext string) string {
	ext = filepath.Ext(ext)
	switch ext {
	case ".mp3":
		return "audio/mpeg"
	case ".flac":
		return "audio/flac"
	case ".wav":
		return "audio/wav"
	case ".ogg":
		return "audio/ogg"
	case ".m4a", ".aac":
		return "audio/m4a"
	default:
		return "application/octet-stream"
	}
}

// GenerateTrackKey génère une clé S3 pour un track
func (s *TrackStorageService) GenerateTrackKey(userID, trackID int64, filename string) string {
	ext := filepath.Ext(filename)
	if ext == "" {
		ext = ".mp3" // Par défaut
	}
	uniqueFilename := fmt.Sprintf("%s%s", uuid.New().String(), ext)
	return fmt.Sprintf("tracks/%d/%d/%s", userID, trackID, uniqueFilename)
}
