package services

import (
	"fmt"
	"io"
	"mime/multipart"
	"strings"

	"veza-backend-api/internal/utils"
)

const (
	// MaxTrackSize limite maximale de taille pour un fichier audio (100MB)
	MaxTrackSize = 100 * 1024 * 1024
	// MinTrackDuration durée minimale d'un track en secondes (1 seconde)
	MinTrackDuration = 1
	// MaxTrackDuration durée maximale d'un track en secondes (3 heures)
	MaxTrackDuration = 3 * 60 * 60
)

// Formats audio supportés
var AllowedFormats = []string{"audio/mpeg", "audio/flac", "audio/wav", "audio/ogg", "audio/vorbis"}

// Codecs audio supportés
var AllowedCodecs = []string{"mp3", "flac", "pcm", "vorbis", "aac"}

// TrackValidationService gère la validation des fichiers audio
type TrackValidationService struct{}

// NewTrackValidationService crée un nouveau service de validation
func NewTrackValidationService() *TrackValidationService {
	return &TrackValidationService{}
}

// ValidateFormat valide le format du fichier en utilisant les magic bytes
func (s *TrackValidationService) ValidateFormat(fileHeader *multipart.FileHeader) error {
	file, err := fileHeader.Open()
	if err != nil {
		return fmt.Errorf("failed to open file: %w", err)
	}
	defer file.Close()

	// Lire les premiers bytes pour vérifier les magic bytes
	magicBytes := make([]byte, 12)
	n, err := file.Read(magicBytes)
	if err != nil && err != io.EOF {
		return fmt.Errorf("failed to read file: %w", err)
	}

	if n < 4 {
		return fmt.Errorf("file too small to validate format")
	}

	// Valider les magic bytes
	if err := s.validateMagicBytes(magicBytes[:n]); err != nil {
		return err
	}

	return nil
}

// validateMagicBytes valide les magic bytes pour les formats audio supportés
func (s *TrackValidationService) validateMagicBytes(magicBytes []byte) error {
	if len(magicBytes) < 4 {
		return fmt.Errorf("insufficient data for magic byte validation")
	}

	// MP3: ID3v2 (starts with "ID3") or MPEG frame sync (0xFF 0xFB/E/F)
	if len(magicBytes) >= 3 && strings.HasPrefix(string(magicBytes[:3]), "ID3") {
		return nil
	}
	if magicBytes[0] == 0xFF && (magicBytes[1] == 0xFB || magicBytes[1] == 0xF3 || magicBytes[1] == 0xF2 || (magicBytes[1]&0xE0) == 0xE0) {
		return nil
	}

	// FLAC: "fLaC" (starts at offset 4 after "fLaC" stream marker)
	if len(magicBytes) >= 4 && string(magicBytes[:4]) == "fLaC" {
		return nil
	}

	// WAV: "RIFF" followed by "WAVE"
	if len(magicBytes) >= 8 && string(magicBytes[:4]) == "RIFF" && string(magicBytes[8:12]) == "WAVE" {
		return nil
	}
	if len(magicBytes) >= 4 && string(magicBytes[:4]) == "RIFF" {
		// Check for WAVE in the next 4 bytes if available
		if len(magicBytes) >= 8 && string(magicBytes[4:8]) == "WAVE" {
			return nil
		}
		// If we have RIFF, check further for WAVE
		additionalBytes := make([]byte, 4)
		if _, err := io.ReadFull(strings.NewReader(string(magicBytes[4:])), additionalBytes); err == nil {
			if string(additionalBytes) == "WAVE" {
				return nil
			}
		}
	}

	// OGG: "OggS"
	if len(magicBytes) >= 4 && string(magicBytes[:4]) == "OggS" {
		return nil
	}

	// M4A/AAC: "ftyp" avec "M4A" ou "mp4"
	if len(magicBytes) >= 8 {
		magicStr := string(magicBytes)
		if strings.Contains(magicStr, "ftyp") {
			if strings.Contains(magicStr, "M4A") || strings.Contains(magicStr, "mp4") {
				return nil
			}
		}
	}

	return fmt.Errorf("invalid audio file format: unsupported format or corrupted file")
}

// ValidateFileSize valide la taille du fichier
func (s *TrackValidationService) ValidateFileSize(fileHeader *multipart.FileHeader) error {
	if fileHeader.Size == 0 {
		return fmt.Errorf("file is empty")
	}

	if fileHeader.Size > MaxTrackSize {
		return fmt.Errorf("file size exceeds maximum allowed size of 100MB")
	}

	return nil
}

// ValidateDuration valide la durée d'un track
func (s *TrackValidationService) ValidateDuration(duration int) error {
	if duration < MinTrackDuration {
		return fmt.Errorf("track duration is too short: minimum %d seconds required", MinTrackDuration)
	}

	if duration > MaxTrackDuration {
		return fmt.Errorf("track duration is too long: maximum %d seconds (3 hours) allowed", MaxTrackDuration)
	}

	return nil
}

// ValidateCodec valide le codec audio
func (s *TrackValidationService) ValidateCodec(codec string) error {
	if codec == "" {
		return fmt.Errorf("codec is required")
	}

	codecLower := strings.ToLower(codec)
	for _, allowedCodec := range AllowedCodecs {
		if codecLower == strings.ToLower(allowedCodec) {
			return nil
		}
	}

	return fmt.Errorf("unsupported codec: %s. Allowed codecs: %s", codec, strings.Join(AllowedCodecs, ", "))
}

// TrackValidationResult représente le résultat d'une validation complète
type TrackValidationResult struct {
	Valid    bool
	Format   string
	Codec    string
	Duration int
	Errors   []string
}

// ValidateTrackFile combine toutes les validations pour un fichier audio
func (s *TrackValidationService) ValidateTrackFile(fileHeader *multipart.FileHeader, duration int, codec string) (*TrackValidationResult, error) {
	result := &TrackValidationResult{
		Valid:    true,
		Errors:   []string{},
		Duration: duration,
		Codec:    codec,
	}

	// Valider la taille
	if err := s.ValidateFileSize(fileHeader); err != nil {
		result.Valid = false
		result.Errors = append(result.Errors, err.Error())
	}

	// Valider le format (magic bytes)
	if err := s.ValidateFormat(fileHeader); err != nil {
		result.Valid = false
		result.Errors = append(result.Errors, err.Error())
	} else {
		// Déterminer le format détecté
		result.Format = s.detectFormat(fileHeader)
	}

	// Valider la durée si fournie
	if duration > 0 {
		if err := s.ValidateDuration(duration); err != nil {
			result.Valid = false
			result.Errors = append(result.Errors, err.Error())
		}
	}

	// Valider le codec si fourni
	if codec != "" {
		if err := s.ValidateCodec(codec); err != nil {
			result.Valid = false
			result.Errors = append(result.Errors, err.Error())
		}
	}

	if !result.Valid {
		return result, fmt.Errorf("validation failed: %s", strings.Join(result.Errors, "; "))
	}

	return result, nil
}

// detectFormat détecte le format du fichier à partir des magic bytes
func (s *TrackValidationService) detectFormat(fileHeader *multipart.FileHeader) string {
	file, err := fileHeader.Open()
	if err != nil {
		return "unknown"
	}
	defer file.Close()

	magicBytes := make([]byte, 12)
	n, err := file.Read(magicBytes)
	if err != nil || n < 4 {
		return "unknown"
	}

	// MP3
	if strings.HasPrefix(string(magicBytes[:utils.Min(3, n)]), "ID3") || (magicBytes[0] == 0xFF && (magicBytes[1]&0xE0) == 0xE0) {
		return "audio/mpeg"
	}

	// FLAC
	if n >= 4 && string(magicBytes[:4]) == "fLaC" {
		return "audio/flac"
	}

	// WAV
	if n >= 4 && string(magicBytes[:4]) == "RIFF" {
		return "audio/wav"
	}

	// OGG
	if n >= 4 && string(magicBytes[:4]) == "OggS" {
		return "audio/ogg"
	}

	// M4A/AAC
	if n >= 8 {
		magicStr := string(magicBytes)
		if strings.Contains(magicStr, "ftyp") && (strings.Contains(magicStr, "M4A") || strings.Contains(magicStr, "mp4")) {
			return "audio/m4a"
		}
	}

	return "unknown"
}

// min est maintenant défini dans internal/utils/math.go
// Import: veza-backend-api/internal/utils
