package handlers

import (
	"context"
	"fmt"
	"mime/multipart"
	"net/http"
	"strings"
	"time"

	apperrors "veza-backend-api/internal/errors"
	"veza-backend-api/internal/services"
	"veza-backend-api/internal/upload"

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

// UploadRequest requête pour upload de fichier
// DEPRECATED: Use upload.StandardUploadRequest instead
// INT-015: Kept for backward compatibility during migration
type UploadRequest struct {
	TrackID  string `form:"track_id" binding:"required"`
	FileType string `form:"file_type" binding:"required,oneof=audio image video"`
	Title    string `form:"title" binding:"required,min=1,max=255"`
	Artist   string `form:"artist" binding:"required,min=1,max=255"`
	Duration int    `form:"duration" binding:"min=0"`
	Metadata string `form:"metadata"`
}

// UploadResponse réponse pour upload
// DEPRECATED: Use upload.StandardUploadResponse instead
// INT-015: Kept for backward compatibility during migration
type UploadResponse struct {
	ID        uuid.UUID `json:"id"`
	TrackID   uuid.UUID `json:"track_id"`
	FileName  string    `json:"file_name"`
	FileSize  int64     `json:"file_size"`
	FileType  string    `json:"file_type"`
	Checksum  string    `json:"checksum"`
	Status    string    `json:"status"`
	CreatedAt time.Time `json:"created_at"`
}

// UploadValidatorInterface définit les méthodes nécessaires pour UploadValidator
type UploadValidatorInterface interface {
	ValidateFile(ctx context.Context, fileHeader *multipart.FileHeader, fileType string) (*services.ValidationResult, error)
	GetFileTypeFromPath(filename string) string
}

// UploadAuditServiceInterface définit les méthodes nécessaires pour AuditService dans le contexte d'upload
type UploadAuditServiceInterface interface {
	LogUpload(ctx context.Context, userID uuid.UUID, resourceID uuid.UUID, fileName string, fileSize int64, ipAddress, userAgent string) error
	LogDeletion(ctx context.Context, userID uuid.UUID, resource string, resourceID uuid.UUID, ipAddress, userAgent string) error
}

// TrackUploadServiceInterface définit les méthodes nécessaires pour TrackUploadService
type TrackUploadServiceInterface interface {
	GetUploadStats(ctx context.Context, userID uuid.UUID) (map[string]interface{}, error)
}

// UploadHandler gère les uploads de fichiers
type UploadHandler struct {
	uploadValidator    UploadValidatorInterface
	auditService       UploadAuditServiceInterface
	trackUploadService TrackUploadServiceInterface
	logger             *zap.Logger
	uploadSemaphore    chan struct{} // MOD-P2-005: Sémaphore pour limiter uploads simultanés
}

// NewUploadHandler crée un nouveau handler d'upload
// MOD-P2-005: maxConcurrentUploads définit la limite d'uploads simultanés (backpressure)
func NewUploadHandler(
	uploadValidator *services.UploadValidator,
	auditService *services.AuditService,
	trackUploadService *services.TrackUploadService,
	logger *zap.Logger,
	maxConcurrentUploads int,
) *UploadHandler {
	if maxConcurrentUploads <= 0 {
		maxConcurrentUploads = 10 // Valeur par défaut
	}
	return &UploadHandler{
		uploadValidator:    uploadValidator,
		auditService:       auditService,
		trackUploadService: trackUploadService,
		logger:             logger,
		uploadSemaphore:    make(chan struct{}, maxConcurrentUploads), // MOD-P2-005: Sémaphore bufferisé
	}
}

// NewUploadHandlerWithInterface crée un nouveau handler d'upload avec des interfaces (pour les tests)
func NewUploadHandlerWithInterface(
	uploadValidator UploadValidatorInterface,
	auditService UploadAuditServiceInterface,
	trackUploadService TrackUploadServiceInterface,
	logger *zap.Logger,
	maxConcurrentUploads int,
) *UploadHandler {
	if maxConcurrentUploads <= 0 {
		maxConcurrentUploads = 10 // Valeur par défaut
	}
	return &UploadHandler{
		uploadValidator:    uploadValidator,
		auditService:       auditService,
		trackUploadService: trackUploadService,
		logger:             logger,
		uploadSemaphore:    make(chan struct{}, maxConcurrentUploads),
	}
}

// UploadFile gère l'upload d'un fichier
// MOD-P2-005: Utilise un sémaphore pour limiter les uploads simultanés (backpressure)
func (uh *UploadHandler) UploadFile() gin.HandlerFunc {
	return func(c *gin.Context) {
		// MOD-P2-005: Acquérir le sémaphore (bloque si limite atteinte)
		select {
		case uh.uploadSemaphore <- struct{}{}:
			// Sémaphore acquis, continuer
			defer func() { <-uh.uploadSemaphore }() // Libérer le sémaphore à la fin
		case <-c.Request.Context().Done():
			// Contexte annulé (timeout, etc.)
			return
		default:
			// Sémaphore plein, retourner 503 Service Unavailable
			uh.logger.Warn("Upload rejected: too many concurrent uploads",
				zap.String("user_id", c.GetString("user_id")),
				zap.String("ip", c.ClientIP()),
			)
			c.Header("Retry-After", "30") // Suggérer de réessayer après 30 secondes
			// INT-015: Use standardized error format
			c.JSON(http.StatusServiceUnavailable, gin.H{
				"success": false,
				"error": gin.H{
					"code":    upload.ErrorCodeTooManyConcurrent,
					"message": "Too many concurrent uploads. Please try again later.",
				},
			})
			return
		}

		// Récupérer l'ID utilisateur depuis le contexte
		userIDInterface, exists := c.Get("user_id")
		if !exists {
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.NewUnauthorizedError("User not authenticated"))
			return
		}

		userID, ok := userIDInterface.(uuid.UUID)
		if !ok {
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.New(apperrors.ErrCodeInternal, "Invalid user ID type"))
			return
		}
		// Parser la requête multipart
		var req UploadRequest
		if err := c.ShouldBind(&req); err != nil {
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, err.Error()))
			return
		}

		trackID, err := uuid.Parse(req.TrackID)
		if err != nil {
			RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "Invalid track ID"))
			return
		}

		// Récupérer le fichier
		fileHeader, err := c.FormFile("file")
		if err != nil {
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "No file provided"))
			return
		}

		// MOD-P1-001: Valider le fichier AVANT toute persistance (scan ClamAV inclus)
		validationResult, err := uh.uploadValidator.ValidateFile(c.Request.Context(), fileHeader, req.FileType)
		if err != nil {
			// MOD-P1-001-REFINEMENT: Détecter erreur ClamAV unavailable et retourner 503
			if strings.Contains(err.Error(), "clamav_unavailable") {
				uh.logger.Warn("Upload rejected: ClamAV unavailable",
					zap.String("user_id", userID.String()),
					zap.String("file_name", fileHeader.Filename),
					zap.Error(err),
				)
				c.JSON(http.StatusServiceUnavailable, gin.H{
					"error":   "Virus scanning service is temporarily unavailable",
					"message": "Uploads are disabled for security reasons until the scanning service is restored",
					"code":    "SERVICE_UNAVAILABLE",
				})
				return
			}
			uh.logger.Error("File validation failed",
				zap.Error(err),
				zap.String("user_id", userID.String()),
				zap.String("file_name", fileHeader.Filename),
			)
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.New(apperrors.ErrCodeInternal, "File validation failed"))
			return
		}

		// MOD-P1-001: Détecter virus détecté (code 422) vs autres erreurs
		if validationResult.Quarantined || (err != nil && strings.Contains(err.Error(), "clamav_infected")) {
			uh.logger.Warn("File rejected: virus detected",
				zap.String("user_id", userID.String()),
				zap.String("file_name", fileHeader.Filename),
				zap.String("reason", validationResult.Error),
			)
			c.JSON(http.StatusUnprocessableEntity, gin.H{
				"error":   "File rejected: virus detected",
				"details": validationResult.Error,
				"code":    "VIRUS_DETECTED",
			})
			return
		}

		// Vérifier si le fichier est valide
		if !validationResult.Valid {
			uh.logger.Warn("Invalid file uploaded",
				zap.String("user_id", userID.String()),
				zap.String("file_name", fileHeader.Filename),
				zap.String("error", validationResult.Error),
			)
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, validationResult.Error))
			return
		}

		// MOD-P1-001: Détecter erreur de scan ClamAV (timeout, connexion, etc.)
		if err != nil && strings.Contains(err.Error(), "clamav_scan_error") {
			uh.logger.Error("Upload rejected: ClamAV scan error",
				zap.String("user_id", userID.String()),
				zap.String("file_name", fileHeader.Filename),
				zap.Error(err),
			)
			c.JSON(http.StatusServiceUnavailable, gin.H{
				"error":   "Virus scan failed",
				"message": "Unable to complete virus scan. Upload rejected for security.",
				"code":    "SCAN_ERROR",
			})
			return
		}

		// Créer l'enregistrement en base de données
		// Note: Dans un vrai environnement, il faudrait sauvegarder le fichier
		// et créer l'enregistrement dans la table tracks
		uploadID := uuid.New()

		// Log l'upload dans l'audit
		err = uh.auditService.LogUpload(
			c.Request.Context(),
			userID,
			trackID,
			fileHeader.Filename,
			validationResult.FileSize,
			c.ClientIP(),
			c.GetHeader("User-Agent"),
		)
		if err != nil {
			uh.logger.Error("Failed to log upload audit",
				zap.Error(err),
				zap.String("user_id", userID.String()),
			)
			// Ne pas faire échouer l'upload pour une erreur d'audit
		}

		uh.logger.Info("File uploaded successfully",
			zap.String("user_id", userID.String()),
			zap.String("upload_id", uploadID.String()),
			zap.String("file_name", fileHeader.Filename),
			zap.Int64("file_size", validationResult.FileSize),
			zap.String("file_type", validationResult.FileType),
		)

		// INT-015: Return standardized upload response
		now := time.Now()
		virusScanResult := "clean"
		response := &upload.StandardUploadResponse{
			ID:              uploadID,
			TrackID:         &trackID,
			FileName:        fileHeader.Filename,
			FileSize:        validationResult.FileSize,
			FileType:        validationResult.FileType,
			MimeType:        fileHeader.Header.Get("Content-Type"),
			Checksum:        validationResult.Checksum,
			Status:          upload.UploadStatusCompleted,
			Progress:        100,
			BytesUploaded:   validationResult.FileSize,
			StoragePath:     "", // Will be set by storage service
			StorageProvider: "s3",
			IsProcessed:     false,
			VirusScanned:    true,
			VirusScanResult: &virusScanResult,
			VirusScannedAt:  &now,
			CreatedAt:       now,
			UpdatedAt:       now,
		}

		RespondSuccess(c, http.StatusCreated, response)
	}
}

// GetUploadStatus récupère le statut d'un upload
func (uh *UploadHandler) GetUploadStatus() gin.HandlerFunc {
	return func(c *gin.Context) {
		uploadIDStr := c.Param("id")
		uploadID, err := uuid.Parse(uploadIDStr)
		if err != nil {
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "Invalid upload ID"))
			return
		}

		// Récupérer le statut depuis la base de données
		// Note: Dans un vrai environnement, il faudrait interroger la DB
		RespondSuccess(c, http.StatusOK, gin.H{
			"id":       uploadID,
			"status":   "completed",
			"progress": 100,
		})
	}
}

// DeleteUpload supprime un upload
func (uh *UploadHandler) DeleteUpload() gin.HandlerFunc {
	return func(c *gin.Context) {
		// Récupérer l'ID utilisateur depuis le contexte
		userIDInterface, exists := c.Get("user_id")
		if !exists {
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.NewUnauthorizedError("User not authenticated"))
			return
		}

		userID, ok := userIDInterface.(uuid.UUID)
		if !ok {
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.New(apperrors.ErrCodeInternal, "Invalid user ID type"))
			return
		}

		uploadIDStr := c.Param("id")
		uploadID, err := uuid.Parse(uploadIDStr)
		if err != nil {
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "Invalid upload ID"))
			return
		}

		// Log la suppression dans l'audit
		err = uh.auditService.LogDeletion(
			c.Request.Context(),
			userID,
			"upload",
			uploadID,
			c.ClientIP(),
			c.GetHeader("User-Agent"),
		)
		if err != nil {
			uh.logger.Error("Failed to log deletion audit",
				zap.Error(err),
				zap.String("user_id", userID.String()),
			)
		}

		uh.logger.Info("Upload deleted",
			zap.String("user_id", userID.String()),
			zap.String("upload_id", uploadID.String()),
		)

		RespondSuccess(c, http.StatusOK, gin.H{
			"message": "Upload deleted successfully",
		})
	}
}

// GetUploadStats récupère les statistiques d'upload
// GET /api/v1/uploads/stats
// BE-API-032: Implement upload stats endpoint
func (uh *UploadHandler) GetUploadStats() gin.HandlerFunc {
	return func(c *gin.Context) {
		// Récupérer l'ID utilisateur depuis le contexte
		userID, ok := GetUserIDUUID(c)
		if !ok {
			return // Erreur déjà envoyée par GetUserIDUUID
		}

		// Récupérer les statistiques depuis la base de données
		if uh.trackUploadService == nil {
			uh.logger.Error("TrackUploadService not available")
			RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Upload stats service not available", nil))
			return
		}

		stats, err := uh.trackUploadService.GetUploadStats(c.Request.Context(), userID)
		if err != nil {
			uh.logger.Error("Failed to get upload stats",
				zap.Error(err),
				zap.String("user_id", userID.String()),
			)
			RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get upload stats", err))
			return
		}

		// BE-API-032: Standardize response format
		RespondSuccess(c, http.StatusOK, gin.H{
			"user_id": userID,
			"stats":   stats,
		})
	}
}

// ValidateFileType valide le type de fichier
func (uh *UploadHandler) ValidateFileType() gin.HandlerFunc {
	return func(c *gin.Context) {
		fileType := c.Query("type")
		if fileType == "" {
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "File type parameter required"))
			return
		}

		// Vérifier si le type est supporté
		supportedTypes := []string{"audio", "image", "video"}
		isSupported := false
		for _, supportedType := range supportedTypes {
			if fileType == supportedType {
				isSupported = true
				break
			}
		}

		if !isSupported {
			c.JSON(http.StatusBadRequest, gin.H{
				"error":           "Unsupported file type",
				"supported_types": supportedTypes,
			})
			return
		}

		RespondSuccess(c, http.StatusOK, gin.H{
			"type":            fileType,
			"supported":       true,
			"supported_types": supportedTypes,
		})
	}
}

// GetUploadLimits récupère les limites d'upload
func (uh *UploadHandler) GetUploadLimits() gin.HandlerFunc {
	return func(c *gin.Context) {
		limits := map[string]interface{}{
			"audio": map[string]interface{}{
				"max_size":       "100MB",
				"max_size_bytes": 100 * 1024 * 1024,
				"allowed_types": []string{
					"audio/mpeg",
					"audio/mp3",
					"audio/wav",
					"audio/flac",
					"audio/aac",
					"audio/ogg",
					"audio/m4a",
				},
			},
			"image": map[string]interface{}{
				"max_size":       "10MB",
				"max_size_bytes": 10 * 1024 * 1024,
				"allowed_types": []string{
					"image/jpeg",
					"image/png",
					"image/gif",
					"image/webp",
					"image/svg+xml",
				},
			},
			"video": map[string]interface{}{
				"max_size":       "500MB",
				"max_size_bytes": 500 * 1024 * 1024,
				"allowed_types": []string{
					"video/mp4",
					"video/webm",
					"video/ogg",
					"video/avi",
				},
			},
		}

		RespondSuccess(c, http.StatusOK, gin.H{
			"limits": limits,
		})
	}
}

// UploadProgress gère le suivi de progression d'upload
func (uh *UploadHandler) UploadProgress() gin.HandlerFunc {
	return func(c *gin.Context) {
		uploadIDStr := c.Param("id")
		uploadID, err := uuid.Parse(uploadIDStr)
		if err != nil {
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "Invalid upload ID"))
			return
		}

		// Récupérer la progression depuis la base de données
		// Note: Dans un vrai environnement, il faudrait interroger la DB
		progress := map[string]interface{}{
			"upload_id":                uploadID,
			"status":                   "completed",
			"progress":                 100,
			"bytes_uploaded":           0,
			"total_bytes":              0,
			"estimated_time_remaining": 0,
		}

		RespondSuccess(c, http.StatusOK, progress)
	}
}

// BatchUpload gère les uploads multiples
func (uh *UploadHandler) BatchUpload() gin.HandlerFunc {
	return func(c *gin.Context) {
		// Récupérer l'ID utilisateur depuis le contexte
		userIDInterface, exists := c.Get("user_id")
		if !exists {
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.NewUnauthorizedError("User not authenticated"))
			return
		}

		userID, ok := userIDInterface.(uuid.UUID)
		if !ok {
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.New(apperrors.ErrCodeInternal, "Invalid user ID type"))
			return
		}

		// Parser le formulaire multipart
		form, err := c.MultipartForm()
		if err != nil {
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "Invalid multipart form"))
			return
		}

		files := form.File["files"]
		if len(files) == 0 {
			// MOD-P2-003: Utiliser AppError au lieu de gin.H
			RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "No files provided"))
			return
		}

		// Limiter le nombre de fichiers par batch
		maxFiles := 10
		if len(files) > maxFiles {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": fmt.Sprintf("Too many files. Maximum %d files per batch", maxFiles),
			})
			return
		}

		var results []map[string]interface{}
		var errors []string

		for i, fileHeader := range files {
			// Déterminer le type de fichier à partir de l'extension
			fileType := uh.uploadValidator.GetFileTypeFromPath(fileHeader.Filename)
			if fileType == "unknown" {
				errors = append(errors, fmt.Sprintf("File %d (%s): Unknown file type", i+1, fileHeader.Filename))
				continue
			}

			// MOD-P1-001: Valider le fichier AVANT toute persistance
			validationResult, err := uh.uploadValidator.ValidateFile(c.Request.Context(), fileHeader, fileType)
			if err != nil {
				// MOD-P1-001-REFINEMENT: Détecter erreur ClamAV unavailable
				if strings.Contains(err.Error(), "clamav_unavailable") {
					errors = append(errors, fmt.Sprintf("File %d (%s): Virus scanning service unavailable", i+1, fileHeader.Filename))
				} else {
					errors = append(errors, fmt.Sprintf("File %d (%s): Validation error", i+1, fileHeader.Filename))
				}
				continue
			}

			if !validationResult.Valid {
				errors = append(errors, fmt.Sprintf("File %d (%s): %s", i+1, fileHeader.Filename, validationResult.Error))
				continue
			}

			// Créer le résultat
			result := map[string]interface{}{
				"index":     i + 1,
				"file_name": fileHeader.Filename,
				"file_size": validationResult.FileSize,
				"file_type": validationResult.FileType,
				"checksum":  validationResult.Checksum,
				"status":    "validated",
				"upload_id": uuid.New(),
			}

			results = append(results, result)
		}

		uh.logger.Info("Batch upload processed",
			zap.String("user_id", userID.String()),
			zap.Int("total_files", len(files)),
			zap.Int("successful", len(results)),
			zap.Int("errors", len(errors)),
		)

		RespondSuccess(c, http.StatusOK, gin.H{
			"message": "Batch upload processed",
			"results": results,
			"errors":  errors,
			"summary": map[string]interface{}{
				"total_files": len(files),
				"successful":  len(results),
				"errors":      len(errors),
			},
		})
	}
}
