package services

import (
	"context"
	"errors"
	"fmt"

	"github.com/google/uuid"

	"veza-backend-api/internal/models"

	"go.uber.org/zap"
	"gorm.io/gorm"
)

// PlaylistDuplicateService gère la duplication de playlists
// T0495: Create Playlist Duplicate Feature
type PlaylistDuplicateService struct {
	playlistService PlaylistServiceInterface
	db              *gorm.DB
	logger          *zap.Logger
}

// NewPlaylistDuplicateService crée un nouveau service de duplication de playlists
func NewPlaylistDuplicateService(playlistService PlaylistServiceInterface, db *gorm.DB, logger *zap.Logger) *PlaylistDuplicateService {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &PlaylistDuplicateService{
		playlistService: playlistService,
		db:              db,
		logger:          logger,
	}
}

// DuplicatePlaylistRequest représente la requête de duplication
type DuplicatePlaylistRequest struct {
	NewTitle       string `json:"new_title"`
	NewDescription string `json:"new_description,omitempty"`
	IsPublic       *bool  `json:"is_public,omitempty"`
}

// DuplicatePlaylist duplique une playlist avec tous ses tracks
// T0495: Create Playlist Duplicate Feature
// MIGRATION UUID: Completée. playlistID et userID sont des UUIDs.
// Transactionnelle : Toute la duplication (playlist + tracks + compteur) est dans une seule transaction
func (s *PlaylistDuplicateService) DuplicatePlaylist(
	ctx context.Context,
	playlistID uuid.UUID,
	userID uuid.UUID,
	request DuplicatePlaylistRequest,
) (*models.Playlist, error) {
	var newPlaylist *models.Playlist

	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		// 1. VALIDATION : Charger playlist originale (sans tracks pour l'instant)
		var originalPlaylist models.Playlist
		err := tx.First(&originalPlaylist, "id = ?", playlistID).Error
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return errors.New("playlist not found")
			}
			return fmt.Errorf("DuplicatePlaylist: failed to load original playlist: %w", err)
		}

		// 1b. Charger tous les PlaylistTrack de la playlist originale (même si le Track associé est supprimé)
		var playlistTracks []models.PlaylistTrack
		if err := tx.Where("playlist_id = ?", playlistID).Order("position ASC").Find(&playlistTracks).Error; err != nil {
			return fmt.Errorf("DuplicatePlaylist: failed to load playlist tracks: %w", err)
		}

		// 2. VALIDATION : Vérifier que l'utilisateur a accès à la playlist (propriétaire, collaborateur ou publique)
		// Note: On fait cette vérification dans la transaction pour éviter les race conditions
		if originalPlaylist.UserID != userID && !originalPlaylist.IsPublic {
			// Vérifier si l'utilisateur est collaborateur (simplifié pour la transaction)
			// On peut faire une requête simple dans la transaction
			var collaboratorCount int64
			err := tx.Raw("SELECT COUNT(*) FROM playlist_collaborators WHERE playlist_id = ? AND user_id = ?", playlistID, userID).Scan(&collaboratorCount).Error
			if err != nil || collaboratorCount == 0 {
				return errors.New("forbidden: you don't have access to this playlist")
			}
		}

		// 3. DÉTERMINATION : Titre, description, isPublic
		newTitle := request.NewTitle
		if newTitle == "" {
			newTitle = originalPlaylist.Title + " (Copy)"
		}
		newDescription := request.NewDescription
		if newDescription == "" {
			newDescription = originalPlaylist.Description
		}
		isPublic := originalPlaylist.IsPublic
		if request.IsPublic != nil {
			isPublic = *request.IsPublic
		}

		// 4. CRÉATION : Nouvelle playlist (INSERT dans la transaction)
		newPlaylist = &models.Playlist{
			UserID:      userID,
			Title:       newTitle,
			Description: newDescription,
			IsPublic:    isPublic,
			TrackCount:  0, // Sera mis à jour après l'ajout des tracks
		}
		if err := tx.Create(newPlaylist).Error; err != nil {
			return fmt.Errorf("DuplicatePlaylist: failed to create duplicate playlist: %w", err)
		}

		// 5. DUPLICATION : Tous les tracks dans la même transaction
		for i, playlistTrack := range playlistTracks {
			trackID := playlistTrack.TrackID
			if trackID == uuid.Nil {
				return fmt.Errorf("DuplicatePlaylist: track not found for playlist track at position %d", i+1)
			}

			// Vérifier que le track existe toujours dans la base de données (non supprimé)
			var trackExists bool
			if err := tx.Model(&models.Track{}).Select("1").Where("id = ? AND deleted_at IS NULL", trackID).Limit(1).Scan(&trackExists).Error; err != nil {
				return fmt.Errorf("DuplicatePlaylist: failed to verify track existence: %w", err)
			}
			if !trackExists {
				return fmt.Errorf("DuplicatePlaylist: track %s no longer exists", trackID)
			}

			// Créer le PlaylistTrack directement dans la transaction
			newPlaylistTrack := models.PlaylistTrack{
				PlaylistID: newPlaylist.ID,
				TrackID:    trackID,
				Position:   playlistTrack.Position,
				AddedBy:    userID, // Use the userID who is duplicating the playlist
			}
			// Si position <= 0, utiliser l'index + 1
			if newPlaylistTrack.Position <= 0 {
				newPlaylistTrack.Position = i + 1
			}
			if err := tx.Create(&newPlaylistTrack).Error; err != nil {
				return fmt.Errorf("DuplicatePlaylist: failed to add track %s to duplicate: %w", trackID, err)
			}
		}

		// 6. MISE À JOUR : Compteur de tracks (UPDATE dans la transaction)
		trackCount := len(playlistTracks)
		if err := tx.Model(newPlaylist).Update("track_count", trackCount).Error; err != nil {
			return fmt.Errorf("DuplicatePlaylist: failed to update track_count: %w", err)
		}
		newPlaylist.TrackCount = trackCount

		// 7. LOG (dans la transaction, mais ne dépend pas d'états non commit)
		s.logger.Info("Playlist duplicated",
			zap.String("original_playlist_id", playlistID.String()),
			zap.String("new_playlist_id", newPlaylist.ID.String()),
			zap.String("user_id", userID.String()),
			zap.Int("tracks_count", trackCount),
		)

		// 8. RETOUR nil = commit automatique
		return nil
	})

	if err != nil {
		return nil, err // Rollback automatique si erreur
	}

	return newPlaylist, nil
}
