package services

import (
	"context"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/google/uuid"
	"go.uber.org/zap"
	"veza-backend-api/internal/models"
)

var (
	// ErrExportFormatNotSupported est retourné quand le format d'export n'est pas supporté
	ErrExportFormatNotSupported = errors.New("export format not supported")
	// ErrSourceFileNotFound est retourné quand le fichier source n'existe pas
	ErrSourceFileNotFound = errors.New("source file not found")
	// ErrFFmpegNotAvailable est retourné quand ffmpeg n'est pas disponible
	ErrFFmpegNotAvailable = errors.New("ffmpeg not available")
	// ErrExportFailed est retourné quand l'export échoue
	ErrExportFailed = errors.New("export failed")
)

// TrackExportService gère l'export de tracks en différents formats
type TrackExportService struct {
	exportDir string
	logger    *zap.Logger
}

// NewTrackExportService crée un nouveau service d'export de tracks
func NewTrackExportService(exportDir string, logger *zap.Logger) *TrackExportService {
	if logger == nil {
		logger = zap.NewNop()
	}

	// Créer le répertoire d'export s'il n'existe pas
	if err := os.MkdirAll(exportDir, 0755); err != nil {
		logger.Warn("Failed to create export directory", zap.Error(err))
	}

	return &TrackExportService{
		exportDir: exportDir,
		logger:    logger,
	}
}

// ExportTrack exporte un track vers le format spécifié
// Si le fichier exporté existe déjà, il est retourné directement (cache)
// MIGRATION UUID: Completée. TrackID en UUID.
func (s *TrackExportService) ExportTrack(ctx context.Context, track *models.Track, format string) (string, error) {
	// Normaliser le format (minuscules)
	format = strings.ToLower(format)

	// Vérifier que le format est supporté
	if !s.isFormatSupported(format) {
		return "", ErrExportFormatNotSupported
	}

	// Vérifier que le fichier source existe
	if _, err := os.Stat(track.FilePath); os.IsNotExist(err) {
		s.logger.Error("Source file not found",
			zap.String("track_id", track.ID.String()),
			zap.String("file_path", track.FilePath))
		return "", ErrSourceFileNotFound
	}

	// Vérifier si le fichier exporté existe déjà (cache)
	exportPath := s.getExportPath(track.ID, format)
	if _, err := os.Stat(exportPath); err == nil {
		s.logger.Info("Using cached export",
			zap.String("track_id", track.ID.String()),
			zap.String("format", format),
			zap.String("export_path", exportPath))
		return exportPath, nil
	}

	// Si le format source est le même que le format cible, copier le fichier
	if strings.ToLower(track.Format) == format {
		return s.copyTrackFile(track, exportPath)
	}

	// Convertir avec ffmpeg
	return s.convertTrack(ctx, track, format, exportPath)
}

// copyTrackFile copie le fichier source vers le répertoire d'export
func (s *TrackExportService) copyTrackFile(track *models.Track, exportPath string) (string, error) {
	// Créer le répertoire parent si nécessaire
	if err := os.MkdirAll(filepath.Dir(exportPath), 0755); err != nil {
		return "", fmt.Errorf("failed to create export directory: %w", err)
	}

	// Lire le fichier source
	sourceData, err := os.ReadFile(track.FilePath)
	if err != nil {
		return "", fmt.Errorf("failed to read source file: %w", err)
	}

	// Écrire le fichier exporté
	if err := os.WriteFile(exportPath, sourceData, 0644); err != nil {
		return "", fmt.Errorf("failed to write export file: %w", err)
	}

	s.logger.Info("Track file copied",
		zap.String("track_id", track.ID.String()),
		zap.String("export_path", exportPath))

	return exportPath, nil
}

// convertTrack convertit un track vers un format différent en utilisant ffmpeg
func (s *TrackExportService) convertTrack(ctx context.Context, track *models.Track, format string, exportPath string) (string, error) {
	// Vérifier que ffmpeg est disponible
	if !s.isFFmpegAvailable() {
		s.logger.Error("FFmpeg not available")
		return "", ErrFFmpegNotAvailable
	}

	// Créer le répertoire parent si nécessaire
	if err := os.MkdirAll(filepath.Dir(exportPath), 0755); err != nil {
		return "", fmt.Errorf("failed to create export directory: %w", err)
	}

	// Construire la commande ffmpeg
	codec := s.getCodec(format)
	bitrate := s.getBitrate(format)
	quality := s.getQuality(format)

	args := []string{
		"-i", track.FilePath,
		"-y", // Overwrite output file
	}

	// Ajouter les options de codec
	if codec != "" {
		args = append(args, "-codec:a", codec)
	}

	// Ajouter le bitrate pour MP3
	if bitrate != "" {
		args = append(args, "-b:a", bitrate)
	}

	// Ajouter la qualité pour FLAC
	if quality != "" {
		args = append(args, "-compression_level", quality)
	}

	// Ajouter le fichier de sortie
	args = append(args, exportPath)

	// Créer la commande avec timeout
	cmd := exec.CommandContext(ctx, "ffmpeg", args...)

	// Capturer stderr pour les logs
	var stderr strings.Builder
	cmd.Stderr = &stderr

	// Exécuter la conversion
	startTime := time.Now()
	err := cmd.Run()
	duration := time.Since(startTime)

	if err != nil {
		s.logger.Error("FFmpeg conversion failed",
			zap.String("track_id", track.ID.String()),
			zap.String("format", format),
			zap.String("stderr", stderr.String()),
			zap.Error(err),
			zap.Duration("duration", duration))
		return "", fmt.Errorf("%w: %v", ErrExportFailed, err)
	}

	// Vérifier que le fichier exporté existe
	if _, err := os.Stat(exportPath); os.IsNotExist(err) {
		return "", fmt.Errorf("%w: output file was not created", ErrExportFailed)
	}

	s.logger.Info("Track exported successfully",
		zap.String("track_id", track.ID.String()),
		zap.String("format", format),
		zap.String("export_path", exportPath),
		zap.Duration("duration", duration))

	return exportPath, nil
}

// getExportPath retourne le chemin du fichier exporté
func (s *TrackExportService) getExportPath(trackID uuid.UUID, format string) string {
	filename := fmt.Sprintf("%s.%s", trackID.String(), format)
	return filepath.Join(s.exportDir, filename)
}

// isFormatSupported vérifie si le format est supporté
func (s *TrackExportService) isFormatSupported(format string) bool {
	supportedFormats := []string{"mp3", "flac", "wav", "ogg", "aac", "m4a"}
	format = strings.ToLower(format)
	for _, f := range supportedFormats {
		if f == format {
			return true
		}
	}
	return false
}

// isFFmpegAvailable vérifie si ffmpeg est disponible
func (s *TrackExportService) isFFmpegAvailable() bool {
	cmd := exec.Command("ffmpeg", "-version")
	if err := cmd.Run(); err != nil {
		return false
	}
	return true
}

// getCodec retourne le codec audio approprié pour le format
func (s *TrackExportService) getCodec(format string) string {
	switch strings.ToLower(format) {
	case "mp3":
		return "libmp3lame"
	case "flac":
		return "flac"
	case "wav":
		return "pcm_s16le"
	case "ogg":
		return "libvorbis"
	case "aac", "m4a":
		return "aac"
	default:
		return "copy"
	}
}

// getBitrate retourne le bitrate approprié pour le format
func (s *TrackExportService) getBitrate(format string) string {
	switch strings.ToLower(format) {
	case "mp3":
		return "192k" // Bitrate par défaut pour MP3
	case "aac", "m4a":
		return "128k" // Bitrate par défaut pour AAC
	default:
		return "" // Pas de bitrate pour les formats lossless
	}
}

// getQuality retourne le niveau de qualité/compression pour le format
func (s *TrackExportService) getQuality(format string) string {
	switch strings.ToLower(format) {
	case "flac":
		return "5" // Niveau de compression FLAC (0-8, 5 est un bon compromis)
	default:
		return "" // Pas de paramètre de qualité pour les autres formats
	}
}

// DeleteExport supprime un fichier exporté du cache
func (s *TrackExportService) DeleteExport(trackID uuid.UUID, format string) error {
	exportPath := s.getExportPath(trackID, format)
	if err := os.Remove(exportPath); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("failed to delete export file: %w", err)
	}
	return nil
}

// DeleteAllExports supprime tous les exports d'un track
func (s *TrackExportService) DeleteAllExports(trackID uuid.UUID) error {
	supportedFormats := []string{"mp3", "flac", "wav", "ogg", "aac", "m4a"}
	for _, format := range supportedFormats {
		if err := s.DeleteExport(trackID, format); err != nil {
			// Log l'erreur mais continue avec les autres formats
			s.logger.Warn("Failed to delete export",
				zap.String("track_id", trackID.String()),
				zap.String("format", format),
				zap.Error(err))
		}
	}
	return nil
}
