package services

import (
	"context"
	"errors"
	"fmt"
	"github.com/google/uuid"
	"io"
	"os"
	"path/filepath"

	"go.uber.org/zap"
	"gorm.io/gorm"
	"veza-backend-api/internal/models"
)

var (
	// ErrVersionNotFound est retourné quand une version n'est pas trouvée
	ErrVersionNotFound = errors.New("version not found")
)

// TrackVersionService gère le versioning de tracks
type TrackVersionService struct {
	db        *gorm.DB
	logger    *zap.Logger
	uploadDir string
}

// NewTrackVersionService crée un nouveau service de versioning de tracks
func NewTrackVersionService(db *gorm.DB, logger *zap.Logger, uploadDir string) *TrackVersionService {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &TrackVersionService{
		db:        db,
		logger:    logger,
		uploadDir: uploadDir,
	}
}

// CreateVersionParams représente les paramètres pour créer une nouvelle version
type CreateVersionParams struct {
	FilePath  string
	FileSize  int64
	Changelog string
}

// CreateVersion crée une nouvelle version d'un track
// MIGRATION UUID: Completée. UserID et TrackID en UUID.
func (s *TrackVersionService) CreateVersion(ctx context.Context, trackID uuid.UUID, userID uuid.UUID, params CreateVersionParams) (*models.TrackVersion, error) {
	// Vérifier que le track existe et appartient à l'utilisateur
	var track models.Track
	if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrTrackNotFound
		}
		return nil, fmt.Errorf("failed to get track: %w", err)
	}

	if track.UserID != userID {
		return nil, ErrForbidden
	}

	// Trouver le prochain numéro de version
	var maxVersion int
	if err := s.db.WithContext(ctx).Model(&models.TrackVersion{}).
		Where("track_id = ?", trackID).
		Select("COALESCE(MAX(version_number), 0)").
		Scan(&maxVersion).Error; err != nil {
		return nil, fmt.Errorf("failed to get max version number: %w", err)
	}

	nextVersion := maxVersion + 1

	// Créer la nouvelle version
	version := &models.TrackVersion{
		TrackID:       trackID,
		VersionNumber: nextVersion,
		FilePath:      params.FilePath,
		FileSize:      params.FileSize,
		Changelog:     params.Changelog,
	}

	if err := s.db.WithContext(ctx).Create(version).Error; err != nil {
		return nil, fmt.Errorf("failed to create version: %w", err)
	}

	s.logger.Info("Track version created",
		zap.String("track_id", trackID.String()),
		zap.String("version_id", version.ID.String()),
		zap.Int("version_number", nextVersion),
		zap.String("user_id", userID.String()),
	)

	return version, nil
}

// GetVersion récupère une version spécifique d'un track
func (s *TrackVersionService) GetVersion(ctx context.Context, trackID uuid.UUID, versionID uuid.UUID) (*models.TrackVersion, error) {
	var version models.TrackVersion
	if err := s.db.WithContext(ctx).
		Where("id = ? AND track_id = ?", versionID, trackID).
		First(&version).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrVersionNotFound
		}
		return nil, fmt.Errorf("failed to get version: %w", err)
	}

	return &version, nil
}

// GetVersionByNumber récupère une version par son numéro
func (s *TrackVersionService) GetVersionByNumber(ctx context.Context, trackID uuid.UUID, versionNumber int) (*models.TrackVersion, error) {
	var version models.TrackVersion
	if err := s.db.WithContext(ctx).
		Where("track_id = ? AND version_number = ?", trackID, versionNumber).
		First(&version).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrVersionNotFound
		}
		return nil, fmt.Errorf("failed to get version: %w", err)
	}

	return &version, nil
}

// ListVersions récupère toutes les versions d'un track
func (s *TrackVersionService) ListVersions(ctx context.Context, trackID uuid.UUID) ([]models.TrackVersion, error) {
	var versions []models.TrackVersion
	if err := s.db.WithContext(ctx).
		Where("track_id = ?", trackID).
		Order("version_number DESC").
		Find(&versions).Error; err != nil {
		return nil, fmt.Errorf("failed to list versions: %w", err)
	}

	return versions, nil
}

// RestoreVersion restaure une version spécifique (copie le fichier de la version vers le track actuel)
// MIGRATION UUID: Completée. UserID et TrackID en UUID.
func (s *TrackVersionService) RestoreVersion(ctx context.Context, trackID uuid.UUID, versionID uuid.UUID, userID uuid.UUID) error {
	// Vérifier que le track existe et appartient à l'utilisateur
	var track models.Track
	if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return ErrTrackNotFound
		}
		return fmt.Errorf("failed to get track: %w", err)
	}

	if track.UserID != userID {
		return ErrForbidden
	}

	// Récupérer la version
	version, err := s.GetVersion(ctx, trackID, versionID)
	if err != nil {
		return err
	}

	// Vérifier que le fichier de la version existe
	if _, err := os.Stat(version.FilePath); os.IsNotExist(err) {
		return fmt.Errorf("version file not found: %s", version.FilePath)
	}

	// Sauvegarder l'ancien fichier du track comme backup (optionnel, on pourrait créer une version automatique)
	// Pour l'instant, on remplace directement

	// Copier le fichier de la version vers le track
	if err := copyFile(version.FilePath, track.FilePath); err != nil {
		return fmt.Errorf("failed to restore version file: %w", err)
	}

	// Mettre à jour les métadonnées du track avec les informations de la version
	updates := map[string]interface{}{
		"file_size": version.FileSize,
	}

	if err := s.db.WithContext(ctx).Model(&track).Updates(updates).Error; err != nil {
		return fmt.Errorf("failed to update track: %w", err)
	}

	s.logger.Info("Track version restored",
		zap.String("track_id", trackID.String()),
		zap.String("version_id", versionID.String()),
		zap.Int("version_number", version.VersionNumber),
		zap.String("user_id", userID.String()),
	)

	return nil
}

// DeleteVersion supprime une version spécifique
// MIGRATION UUID: Completée. UserID et TrackID en UUID.
func (s *TrackVersionService) DeleteVersion(ctx context.Context, trackID uuid.UUID, versionID uuid.UUID, userID uuid.UUID) error {
	// Vérifier que le track existe et appartient à l'utilisateur
	var track models.Track
	if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return ErrTrackNotFound
		}
		return fmt.Errorf("failed to get track: %w", err)
	}

	if track.UserID != userID {
		return ErrForbidden
	}

	// Récupérer la version
	version, err := s.GetVersion(ctx, trackID, versionID)
	if err != nil {
		return err
	}

	// Supprimer le fichier de la version si il existe
	if version.FilePath != "" {
		if err := os.Remove(version.FilePath); err != nil && !os.IsNotExist(err) {
			s.logger.Warn("Failed to delete version file",
				zap.String("version_id", versionID.String()),
				zap.String("file_path", version.FilePath),
				zap.Error(err),
			)
			// On continue même si la suppression du fichier échoue
		}
	}

	// Supprimer la version de la base de données (soft delete)
	if err := s.db.WithContext(ctx).Delete(version).Error; err != nil {
		return fmt.Errorf("failed to delete version: %w", err)
	}

	s.logger.Info("Track version deleted",
		zap.String("track_id", trackID.String()),
		zap.String("version_id", versionID.String()),
		zap.String("user_id", userID.String()),
	)

	return nil
}

// copyFile est une fonction utilitaire pour copier un fichier
func copyFile(src, dst string) error {
	// Créer le répertoire de destination si nécessaire
	dstDir := filepath.Dir(dst)
	if err := os.MkdirAll(dstDir, 0755); err != nil {
		return fmt.Errorf("failed to create destination directory: %w", err)
	}

	sourceFile, err := os.Open(src)
	if err != nil {
		return fmt.Errorf("failed to open source file: %w", err)
	}
	defer sourceFile.Close()

	destinationFile, err := os.Create(dst)
	if err != nil {
		return fmt.Errorf("failed to create destination file: %w", err)
	}
	defer destinationFile.Close()

	_, err = io.Copy(destinationFile, sourceFile)
	if err != nil {
		return fmt.Errorf("failed to copy file: %w", err)
	}

	return nil
}
