package services

import (
	"bytes"
	"crypto/md5"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"path/filepath"
	"strings"
	"time"

	"github.com/dutchcoders/go-clamd"
	"go.uber.org/zap"
)

// UploadValidator service pour valider les uploads de fichiers
type UploadValidator struct {
	logger        *zap.Logger
	clamdClient   *clamd.Clamd
	quarantineDir string
}

// UploadConfig configuration pour les uploads
type UploadConfig struct {
	// Limites de taille
	MaxAudioSize int64 // 100MB
	MaxImageSize int64 // 10MB
	MaxVideoSize int64 // 500MB

	// Types MIME autorisés
	AllowedAudioTypes []string
	AllowedImageTypes []string
	AllowedVideoTypes []string

	// Configuration ClamAV
	ClamAVEnabled bool
	ClamAVAddress string

	// Dossier de quarantaine
	QuarantineDir string
}

// DefaultUploadConfig retourne la configuration par défaut
func DefaultUploadConfig() *UploadConfig {
	return &UploadConfig{
		MaxAudioSize: 100 * 1024 * 1024, // 100MB
		MaxImageSize: 10 * 1024 * 1024,  // 10MB
		MaxVideoSize: 500 * 1024 * 1024, // 500MB

		AllowedAudioTypes: []string{
			"audio/mpeg",
			"audio/mp3",
			"audio/wav",
			"audio/flac",
			"audio/aac",
			"audio/ogg",
			"audio/m4a",
		},
		AllowedImageTypes: []string{
			"image/jpeg",
			"image/png",
			"image/gif",
			"image/webp",
			"image/svg+xml",
		},
		AllowedVideoTypes: []string{
			"video/mp4",
			"video/webm",
			"video/ogg",
			"video/avi",
		},

		ClamAVEnabled: true,
		ClamAVAddress: "localhost:3310",
		QuarantineDir: "/quarantine",
	}
}

// NewUploadValidator crée un nouveau validateur d'upload
func NewUploadValidator(config *UploadConfig, logger *zap.Logger) (*UploadValidator, error) {
	var clamdClient *clamd.Clamd

	if config.ClamAVEnabled {
		clamdClient = clamd.NewClamd(config.ClamAVAddress)
		// Test connection
		if err := clamdClient.Ping(); err != nil {
			logger.Warn("Failed to connect to ClamAV, continuing without virus scanning", zap.Error(err))
			clamdClient = nil
		}
	}

	return &UploadValidator{
		logger:        logger,
		clamdClient:   clamdClient,
		quarantineDir: config.QuarantineDir,
	}, nil
}

// ValidationResult résultat de la validation
type ValidationResult struct {
	Valid       bool
	FileType    string
	FileSize    int64
	Checksum    string
	Error       string
	Quarantined bool
}

// ValidateFile valide un fichier uploadé
func (uv *UploadValidator) ValidateFile(fileHeader *multipart.FileHeader, fileType string) (*ValidationResult, error) {
	result := &ValidationResult{
		FileSize: fileHeader.Size,
	}

	// Ouvrir le fichier
	file, err := fileHeader.Open()
	if err != nil {
		result.Error = "Failed to open file"
		return result, err
	}
	defer file.Close()

	// Lire les premiers bytes pour vérifier le magic number
	header := make([]byte, 512)
	n, err := file.Read(header)
	if err != nil && err != io.EOF {
		result.Error = "Failed to read file header"
		return result, err
	}

	// Reset la position du fichier
	file.Seek(0, 0)

	// Détecter le type MIME réel
	detectedMIME := http.DetectContentType(header[:n])
	result.FileType = detectedMIME

	// Valider le type de fichier
	if !uv.isValidFileType(detectedMIME, fileType) {
		result.Error = fmt.Sprintf("Invalid file type: %s", detectedMIME)
		return result, nil
	}

	// Valider la taille
	if !uv.isValidFileSize(fileHeader.Size, fileType) {
		result.Error = fmt.Sprintf("File too large for type %s", fileType)
		return result, nil
	}

	// Calculer le checksum MD5
	hash := md5.New()
	file.Seek(0, 0)
	if _, err := io.Copy(hash, file); err != nil {
		result.Error = "Failed to calculate checksum"
		return result, err
	}
	result.Checksum = fmt.Sprintf("%x", hash.Sum(nil))

	// Scanner avec ClamAV si disponible
	if uv.clamdClient != nil {
		file.Seek(0, 0)
		scanResult, err := uv.scanWithClamAV(file)
		if err != nil {
			uv.logger.Error("ClamAV scan failed", zap.Error(err))
			// En cas d'erreur de scan, mettre en quarantaine par sécurité
			result.Quarantined = true
			result.Error = "Virus scan failed, file quarantined"
			return result, nil
		}

		if scanResult != nil && scanResult.Status != "OK" {
			result.Quarantined = true
			result.Error = "Virus detected: " + scanResult.Description
			return result, nil
		}
	}

	// Valider l'extension du fichier
	ext := strings.ToLower(filepath.Ext(fileHeader.Filename))
	if !uv.isValidExtension(ext, fileType) {
		result.Error = fmt.Sprintf("Invalid file extension: %s", ext)
		return result, nil
	}

	result.Valid = true
	return result, nil
}

// isValidFileType vérifie si le type MIME est autorisé
func (uv *UploadValidator) isValidFileType(mimeType, fileType string) bool {
	config := DefaultUploadConfig()

	switch fileType {
	case "audio":
		for _, allowed := range config.AllowedAudioTypes {
			if mimeType == allowed {
				return true
			}
		}
	case "image":
		for _, allowed := range config.AllowedImageTypes {
			if mimeType == allowed {
				return true
			}
		}
	case "video":
		for _, allowed := range config.AllowedVideoTypes {
			if mimeType == allowed {
				return true
			}
		}
	}

	return false
}

// isValidFileSize vérifie si la taille du fichier est autorisée
func (uv *UploadValidator) isValidFileSize(size int64, fileType string) bool {
	config := DefaultUploadConfig()

	switch fileType {
	case "audio":
		return size <= config.MaxAudioSize
	case "image":
		return size <= config.MaxImageSize
	case "video":
		return size <= config.MaxVideoSize
	}

	return false
}

// isValidExtension vérifie si l'extension est valide pour le type
func (uv *UploadValidator) isValidExtension(ext, fileType string) bool {
	extensions := map[string][]string{
		"audio": {".mp3", ".wav", ".flac", ".aac", ".ogg", ".m4a"},
		"image": {".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"},
		"video": {".mp4", ".webm", ".ogg", ".avi"},
	}

	if allowedExts, exists := extensions[fileType]; exists {
		for _, allowed := range allowedExts {
			if ext == allowed {
				return true
			}
		}
	}

	return false
}

// scanWithClamAV scanne le fichier avec ClamAV
func (uv *UploadValidator) scanWithClamAV(file io.Reader) (*clamd.ScanResult, error) {
	// Lire tout le fichier en mémoire pour le scan
	var buf bytes.Buffer
	if _, err := io.Copy(&buf, file); err != nil {
		return nil, err
	}

	// Scanner avec ClamAV
	scanChan := make(chan *clamd.ScanResult, 1)
	errChan := make(chan bool, 1)

	go func() {
		uv.clamdClient.ScanStream(&buf, errChan)
	}()

	select {
	case result := <-scanChan:
		return result, nil
	case <-errChan:
		return nil, fmt.Errorf("scan failed")
	}
}

// QuarantineFile met un fichier en quarantaine
func (uv *UploadValidator) QuarantineFile(fileHeader *multipart.FileHeader, reason string) error {
	// Créer le nom de fichier avec timestamp
	timestamp := time.Now().Format("20060102_150405")
	filename := fmt.Sprintf("%s_%s_%s", timestamp, fileHeader.Filename, reason)
	quarantinePath := filepath.Join(uv.quarantineDir, filename)

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

	// Créer le fichier de quarantaine
	// Note: Dans un vrai environnement, il faudrait créer le dossier s'il n'existe pas
	// et gérer les permissions appropriées

	uv.logger.Warn("File quarantined",
		zap.String("original_name", fileHeader.Filename),
		zap.String("quarantine_path", quarantinePath),
		zap.String("reason", reason),
	)

	return nil
}

// GetFileTypeFromPath détermine le type de fichier à partir du chemin
func (uv *UploadValidator) GetFileTypeFromPath(filename string) string {
	ext := strings.ToLower(filepath.Ext(filename))

	audioExts := []string{".mp3", ".wav", ".flac", ".aac", ".ogg", ".m4a"}
	imageExts := []string{".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"}
	videoExts := []string{".mp4", ".webm", ".ogg", ".avi"}

	for _, audioExt := range audioExts {
		if ext == audioExt {
			return "audio"
		}
	}

	for _, imageExt := range imageExts {
		if ext == imageExt {
			return "image"
		}
	}

	for _, videoExt := range videoExts {
		if ext == videoExt {
			return "video"
		}
	}

	return "unknown"
}
