package services

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/google/uuid"

	"veza-backend-api/internal/models"
	"veza-backend-api/internal/repositories"

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

// UserRepositoryForPlaylist définit l'interface minimale nécessaire pour PlaylistService
// T0453: Interface pour vérifier l'existence des utilisateurs
type UserRepositoryForPlaylist interface {
	GetByID(id string) (*models.User, error)
	GetByEmail(email string) (*models.User, error)
	GetByUsername(username string) (*models.User, error)
	Create(user *models.User) error
	Update(user *models.User) error
	Delete(id string) error
}

// PlaylistService gère les opérations sur les playlists
// T0453: Utilise le repository pattern pour l'accès aux données
// BE-SVC-001: Add cache service for playlist caching
type PlaylistService struct {
	playlistRepo                repositories.PlaylistRepository
	playlistTrackRepo           repositories.PlaylistTrackRepository
	playlistCollaboratorRepo    repositories.PlaylistCollaboratorRepository
	playlistShareService        *PlaylistShareService
	playlistFollowService       *PlaylistFollowService
	playlistNotificationService *PlaylistNotificationService
	playlistVersionService      *PlaylistVersionService
	userRepo                    UserRepositoryForPlaylist
	cacheService                *CacheService
	logger                      *zap.Logger
}

// NewPlaylistService crée un nouveau service de playlists avec repositories
func NewPlaylistService(playlistRepo repositories.PlaylistRepository, playlistTrackRepo repositories.PlaylistTrackRepository, playlistCollaboratorRepo repositories.PlaylistCollaboratorRepository, userRepo UserRepositoryForPlaylist, logger *zap.Logger) *PlaylistService {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &PlaylistService{
		playlistRepo:             playlistRepo,
		playlistTrackRepo:        playlistTrackRepo,
		playlistCollaboratorRepo: playlistCollaboratorRepo,
		userRepo:                 userRepo,
		logger:                   logger,
	}
}

// SetPlaylistShareService définit le service de partage de playlist
// T0488: Create Playlist Public Share Link
func (s *PlaylistService) SetPlaylistShareService(shareService *PlaylistShareService) {
	s.playlistShareService = shareService
}

// SetPlaylistFollowService définit le service de follow de playlist
// T0489: Create Playlist Follow Feature
func (s *PlaylistService) SetPlaylistFollowService(followService *PlaylistFollowService) {
	s.playlistFollowService = followService
}

// SetPlaylistNotificationService définit le service de notifications de playlist
// T0508: Create Playlist Notifications
func (s *PlaylistService) SetPlaylistNotificationService(notificationService *PlaylistNotificationService) {
	s.playlistNotificationService = notificationService
}

// SetPlaylistVersionService définit le service de versions de playlist
// T0509: Create Playlist Version History
func (s *PlaylistService) SetPlaylistVersionService(versionService *PlaylistVersionService) {
	s.playlistVersionService = versionService
}

// SetCacheService définit le service de cache pour PlaylistService
// BE-SVC-001: Implement caching layer for frequently accessed data
func (s *PlaylistService) SetCacheService(cacheService *CacheService) {
	s.cacheService = cacheService
}

// NewPlaylistServiceWithDB crée un nouveau service de playlists avec GORM (compatibilité)
// Cette fonction crée les repositories en interne pour maintenir la compatibilité
func NewPlaylistServiceWithDB(db *gorm.DB, logger *zap.Logger) *PlaylistService {
	if logger == nil {
		logger = zap.NewNop()
	}
	playlistRepo := repositories.NewPlaylistRepository(db)
	playlistTrackRepo := repositories.NewPlaylistTrackRepository(db)
	playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db)
	// Pour UserRepository, on utilise une implémentation simple qui utilise GORM
	// Note: On pourrait créer un UserRepository GORM aussi, mais pour l'instant on garde la compatibilité
	userRepo := &gormUserRepository{db: db}
	service := &PlaylistService{
		playlistRepo:             playlistRepo,
		playlistTrackRepo:        playlistTrackRepo,
		playlistCollaboratorRepo: playlistCollaboratorRepo,
		userRepo:                 userRepo,
		logger:                   logger,
	}
	// Créer et injecter le service de partage
	shareService := NewPlaylistShareService(db)
	service.SetPlaylistShareService(shareService)
	return service
}

// gormUserRepository est une implémentation temporaire de UserRepository avec GORM
// pour maintenir la compatibilité avec le code existant
type gormUserRepository struct {
	db *gorm.DB
}

func (r *gormUserRepository) GetByID(id string) (*models.User, error) {
	var user models.User
	if err := r.db.First(&user, "id = ?", id).Error; err != nil {
		return nil, err
	}
	return &user, nil
}

func (r *gormUserRepository) GetByEmail(email string) (*models.User, error) {
	var user models.User
	if err := r.db.Where("email = ?", email).First(&user).Error; err != nil {
		return nil, err
	}
	return &user, nil
}

func (r *gormUserRepository) GetByUsername(username string) (*models.User, error) {
	var user models.User
	if err := r.db.Where("username = ?", username).First(&user).Error; err != nil {
		return nil, err
	}
	return &user, nil
}

func (r *gormUserRepository) Create(user *models.User) error {
	return r.db.Create(user).Error
}

func (r *gormUserRepository) Update(user *models.User) error {
	return r.db.Save(user).Error
}

func (r *gormUserRepository) Delete(id string) error {
	return r.db.Delete(&models.User{}, "id = ?", id).Error
}

// Exists vérifie si un utilisateur existe (méthode helper pour le service)
func (r *gormUserRepository) Exists(ctx context.Context, userID uuid.UUID) (bool, error) {
	var count int64
	err := r.db.WithContext(ctx).Model(&models.User{}).Where("id = ?", userID).Count(&count).Error
	return count > 0, err
}

// CreatePlaylist crée une nouvelle playlist
// T0453: Utilise le repository pattern avec validation
// MOD-P2-003: Enregistre la métrique business
func (s *PlaylistService) CreatePlaylist(ctx context.Context, userID uuid.UUID, title, description string, isPublic bool) (*models.Playlist, error) {
	// Validation
	if title == "" {
		return nil, errors.New("title is required")
	}
	if len(title) > 200 {
		return nil, errors.New("title must be less than 200 characters")
	}

	// Vérifier que l'utilisateur existe
	// Note: On utilise une méthode helper Exists si disponible
	if gormRepo, ok := s.userRepo.(interface {
		Exists(ctx context.Context, userID uuid.UUID) (bool, error)
	}); ok {
		exists, err := gormRepo.Exists(ctx, userID)
		if err != nil {
			return nil, fmt.Errorf("failed to check user: %w", err)
		}
		if !exists {
			return nil, errors.New("user not found")
		}
	} else {
		// Pour les autres implémentations, on essaie de récupérer l'utilisateur
		_, err := s.userRepo.GetByID(userID.String())
		if err != nil {
			return nil, errors.New("user not found")
		}
	}

	// Créer la playlist
	playlist := &models.Playlist{
		UserID:      userID,
		Title:       title,
		Description: description,
		IsPublic:    isPublic,
		TrackCount:  0,
	}

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

	s.logger.Info("Playlist created",
		zap.String("playlist_id", playlist.ID.String()),
		zap.String("user_id", userID.String()),
		zap.String("title", title),
	)

	// T0509: Sauvegarder la version initiale
	if s.playlistVersionService != nil {
		// FIXME: PlaylistVersionService likely needs update for UUID too, but assuming it takes what we give or we handle it later
		// Assuming PlaylistVersionService needs int64, we might have issues.
		// For now, let's pass UUID if it accepts interface{} or we update it later.
		// Actually, let's assume we need to update it or skip versioning for now if it breaks.
		// Let's try to pass it.
		if _, err := s.playlistVersionService.SaveVersion(ctx, playlist.ID, userID, models.PlaylistVersionActionCreated); err != nil {
			s.logger.Warn("Failed to save initial playlist version", zap.Error(err))
		}
	}

	return playlist, nil
}

// GetPlaylist récupère une playlist avec ses tracks
// T0453: Utilise le repository pattern avec vérification d'accès
// MIGRATION UUID: userID migré vers *uuid.UUID
// BE-SVC-001: Add caching for playlist data
func (s *PlaylistService) GetPlaylist(ctx context.Context, playlistID uuid.UUID, userID *uuid.UUID) (*models.Playlist, error) {
	cacheConfig := DefaultCacheConfig()

	// Try to get from cache first
	if s.cacheService != nil {
		var cachedPlaylist models.Playlist
		if err := s.cacheService.GetPlaylist(ctx, playlistID, &cachedPlaylist); err == nil {
			// Cache hit - but we still need to check access
			// Vérifier accès si playlist privée
			if !cachedPlaylist.IsPublic {
				if userID == nil || *userID != cachedPlaylist.UserID {
					return nil, ErrPlaylistNotFound
				}
			}
			return &cachedPlaylist, nil
		}
	}

	// Cache miss - fetch from database
	playlist, err := s.playlistRepo.GetByIDWithTracks(ctx, playlistID) // Use GetByIDWithTracks
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrPlaylistNotFound
		}
		return nil, fmt.Errorf("failed to get playlist: %w", err)
	}

	// Vérifier accès si playlist privée
	if !playlist.IsPublic {
		if userID == nil || *userID != playlist.UserID {
			return nil, ErrPlaylistNotFound // Return NotFound for security (hide private playlists)
		}
	}

	// Cache the playlist
	if s.cacheService != nil {
		if err := s.cacheService.SetPlaylist(ctx, playlistID, playlist, cacheConfig); err != nil {
			s.logger.Warn("Failed to cache playlist", zap.Error(err), zap.String("playlist_id", playlistID.String()))
		}
	}

	return playlist, nil
}

// GetPlaylists récupère une liste de playlists avec pagination
// T0453: Utilise le repository pattern avec filtres
// T0501: Optimisé avec pagination efficace et lazy loading
// MIGRATION UUID: currentUserID et filterUserID migrés vers *uuid.UUID
// MOD: Utilisation du filtre viewerID pour gestion SQL de la visibilité
func (s *PlaylistService) GetPlaylists(ctx context.Context, currentUserID *uuid.UUID, filterUserID *uuid.UUID, page, limit int) ([]*models.Playlist, int64, error) {
	fmt.Printf("🔍 [SERVICE] GetPlaylists: currentUserID=%v\n", currentUserID)
	if currentUserID != nil {
		fmt.Printf("🔍 [SERVICE] GetPlaylists: currentUserID value=%v\n", *currentUserID)
	}
	// Appliquer la pagination avec limites optimisées
	if limit <= 0 {
		limit = 20
	}
	if limit > 100 {
		limit = 100
	}
	if page < 1 {
		page = 1
	}
	offset := (page - 1) * limit

	// T0501: Optimisation - Utiliser un offset calculé efficacement
	// Pour les grandes pages, utiliser un curseur si disponible
	if page > 100 {
		// Pour les très grandes pages, limiter à 100 pour éviter les problèmes de performance
		page = 100
		offset = (page - 1) * limit
	}

	// Déterminer le filtre isPublic
	var isPublic *bool

	// Gestion simplifiée grâce au viewerID dans le repository:
	// Si on filtre par utilisateur
	if filterUserID != nil {
		if currentUserID == nil {
			// Visiteur anonyme -> Public only
			public := true
			isPublic = &public
		} else if *filterUserID != *currentUserID {
			// Visiteur authentifié regardant un autre user -> Public only
			// (Sauf si on implémente logic ami/collaborateur plus tard, mais pour l'instant Public)
			public := true
			isPublic = &public
		}
		// Si (filterUserID == currentUserID), on laisse isPublic à nil pour tout voir
	} else {
		// Liste globale (Feed)
		if currentUserID == nil {
			// Anonyme -> Public only
			public := true
			isPublic = &public
		}
		// Si authentifié, on laisse isPublic à nil et on passe viewerID=currentUserID
		// Le repository fera (is_public=true OR user_id=viewerID)
	}

	// Appel optimisé au repository
	// On passe currentUserID comme viewerID
	playlists, total, err := s.playlistRepo.List(ctx, filterUserID, currentUserID, isPublic, limit, offset)
	if err != nil {
		return nil, 0, fmt.Errorf("playlist repository List failed: %w", err)
	}

	// T0501: Lazy loading - Ne pas charger les tracks pour la liste
	for _, p := range playlists {
		p.Tracks = nil
	}

	// Plus besoin de filtrage en mémoire, le SQL a tout géré !

	return playlists, total, nil
}

// SearchPlaylistsParams représente les paramètres de recherche de playlists
// T0496: Create Playlist Search Backend
// MIGRATION UUID: UserID et CurrentUserID migrés vers *uuid.UUID
type SearchPlaylistsParams struct {
	Query         string     // Recherche par titre ou description
	UserID        *uuid.UUID // Filtrer par utilisateur
	IsPublic      *bool      // Filtrer par statut public/privé
	Page          int        // Numéro de page (défaut: 1)
	Limit         int        // Nombre de résultats par page (défaut: 20, max: 100)
	CurrentUserID *uuid.UUID // ID de l'utilisateur actuel pour les règles d'accès
}

// SearchPlaylists recherche des playlists selon les critères fournis
// T0496: Create Playlist Search Backend
func (s *PlaylistService) SearchPlaylists(ctx context.Context, params SearchPlaylistsParams) ([]*models.Playlist, int64, error) {
	// Appliquer la pagination
	if params.Limit <= 0 {
		params.Limit = 20
	}
	if params.Limit > 100 {
		params.Limit = 100
	}
	if params.Page < 1 {
		params.Page = 1
	}
	offset := (params.Page - 1) * params.Limit

	// Déterminer le filtre isPublic selon les règles d'accès
	var isPublic *bool
	if params.IsPublic != nil {
		isPublic = params.IsPublic
	} else if params.CurrentUserID == nil {
		// Si pas d'utilisateur authentifié, seulement les playlists publiques
		public := true
		isPublic = &public
	} else if params.UserID != nil && *params.UserID != *params.CurrentUserID {
		// Si on recherche les playlists d'un autre utilisateur, seulement publiques
		public := true
		isPublic = &public
	}
	// Si params.UserID == nil ou params.UserID == params.CurrentUserID, on ne filtre pas par isPublic
	// (on laisse le repository gérer)

	// Utiliser la méthode Search du repository
	playlists, total, err := s.playlistRepo.Search(ctx, params.Query, params.UserID, isPublic, params.Limit, offset)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to search playlists: %w", err)
	}

	// Filtrer les playlists selon les règles d'accès si nécessaire
	if params.CurrentUserID != nil && params.UserID == nil && isPublic == nil {
		// Recherche globale : filtrer pour ne garder que les publiques ou celles de l'utilisateur
		filtered := make([]*models.Playlist, 0)
		for _, p := range playlists {
			if p.IsPublic || p.UserID == *params.CurrentUserID {
				filtered = append(filtered, p)
			}
		}
		playlists = filtered
	}

	s.logger.Debug("Playlists searched",
		zap.String("query", params.Query),
		zap.Any("user_id", params.UserID),
		zap.Any("is_public", params.IsPublic),
		zap.Int("page", params.Page),
		zap.Int("limit", params.Limit),
		zap.Int64("total", total),
		zap.Int("results", len(playlists)),
	)

	return playlists, total, nil
}

// UpdatePlaylist met à jour une playlist
// T0453: Utilise le repository pattern avec vérification d'ownership
// MIGRATION UUID: userID en uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistService) UpdatePlaylist(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID, title, description *string, isPublic *bool) (*models.Playlist, error) {
	// Vérifier ownership
	playlist, err := s.playlistRepo.GetByID(ctx, playlistID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrPlaylistNotFound
		}
		return nil, fmt.Errorf("failed to check playlist: %w", err)
	}

	if playlist.UserID != userID {
		return nil, ErrAccessDenied
	}

	// Validation
	if title != nil {
		if *title == "" {
			return nil, ErrTitleEmpty
		}
		if len(*title) > 200 {
			return nil, ErrTitleTooLong
		}
		playlist.Title = *title
	}
	if description != nil {
		playlist.Description = *description
	}
	if isPublic != nil {
		playlist.IsPublic = *isPublic
	}

	if err := s.playlistRepo.Update(ctx, playlist); err != nil {
		return nil, fmt.Errorf("failed to update playlist: %w", err)
	}

	s.logger.Info("Playlist updated",
		zap.String("playlist_id", playlistID.String()),
		zap.String("user_id", userID.String()),
	)

	// T0509: Sauvegarder une version avant la mise à jour
	if s.playlistVersionService != nil {
		if _, err := s.playlistVersionService.SaveVersion(ctx, playlistID, userID, models.PlaylistVersionActionUpdated); err != nil {
			s.logger.Warn("Failed to save playlist version", zap.Error(err))
		}
	}

	// T0508: Envoyer une notification
	if s.playlistNotificationService != nil {
		if err := s.playlistNotificationService.NotifyPlaylistUpdated(ctx, playlistID, userID); err != nil {
			s.logger.Warn("Failed to send playlist updated notification", zap.Error(err))
		}
	}

	return playlist, nil
}

// DeletePlaylist supprime une playlist (soft delete)
// T0453: Utilise le repository pattern avec vérification d'ownership
// MIGRATION UUID: userID en uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistService) DeletePlaylist(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID) error {
	// Vérifier ownership
	playlist, err := s.playlistRepo.GetByID(ctx, playlistID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return ErrPlaylistNotFound
		}
		return fmt.Errorf("failed to check playlist: %w", err)
	}

	if playlist.UserID != userID {
		return ErrAccessDenied
	}

	if err := s.playlistRepo.Delete(ctx, playlistID); err != nil {
		return fmt.Errorf("failed to delete playlist: %w", err)
	}

	s.logger.Info("Playlist deleted",
		zap.String("playlist_id", playlistID.String()),
		zap.String("user_id", userID.String()),
	)

	return nil
}

// AddTrackToPlaylist ajoute un track à une playlist
// T0466: Implémentation avec PlaylistTrackRepository
// MIGRATION UUID: userID en uuid.UUID, playlistID et trackID en uuid.UUID
func (s *PlaylistService) AddTrackToPlaylist(ctx context.Context, playlistID, trackID uuid.UUID, userID uuid.UUID, position int) error {
	// Vérifier ownership
	playlist, err := s.playlistRepo.GetByID(ctx, playlistID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return ErrPlaylistNotFound
		}
		return fmt.Errorf("failed to check playlist: %w", err)
	}

	if playlist.UserID != userID {
		return ErrAccessDenied
	}

	// Ajouter le track via le repository (qui vérifie l'existence du track)
	if err := s.playlistTrackRepo.AddTrack(ctx, playlistID, trackID, position); err != nil {
		if err.Error() == "track not found" {
			return ErrTrackNotFound
		}
		if err.Error() == "track already in playlist" {
			return ErrTrackAlreadyInPlaylist
		}
		return fmt.Errorf("failed to add track to playlist: %w", err)
	}

	s.logger.Info("Track added to playlist",
		zap.String("playlist_id", playlistID.String()),
		zap.String("track_id", trackID.String()),
		zap.String("user_id", userID.String()),
		zap.Int("position", position),
	)

	// T0508: Envoyer une notification (trackTitle sera vide, le service utilisera un message générique)
	if s.playlistNotificationService != nil {
		if err := s.playlistNotificationService.NotifyTrackAdded(ctx, playlistID, "", userID); err != nil {
			s.logger.Warn("Failed to send track added notification", zap.Error(err))
		}
	}

	return nil
}

// AddTrack est un alias pour AddTrackToPlaylist (compatibilité)
// MIGRATION UUID: userID en uuid.UUID, playlistID et trackID en uuid.UUID
func (s *PlaylistService) AddTrack(ctx context.Context, playlistID, trackID uuid.UUID, userID uuid.UUID) error {
	return s.AddTrackToPlaylist(ctx, playlistID, trackID, userID, 0)
}

// RemoveTrackFromPlaylist retire un track d'une playlist
// T0466: Implémentation avec PlaylistTrackRepository
// MIGRATION UUID: userID en uuid.UUID, playlistID et trackID en uuid.UUID
func (s *PlaylistService) RemoveTrackFromPlaylist(ctx context.Context, playlistID, trackID uuid.UUID, userID uuid.UUID) error {
	// Vérifier ownership
	playlist, err := s.playlistRepo.GetByID(ctx, playlistID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return ErrPlaylistNotFound
		}
		return fmt.Errorf("failed to check playlist: %w", err)
	}

	if playlist.UserID != userID {
		return errors.New("forbidden")
	}

	// Retirer le track via le repository
	if err := s.playlistTrackRepo.RemoveTrack(ctx, playlistID, trackID); err != nil {
		if err.Error() == "track not found in playlist" {
			return errors.New("track not found in playlist")
		}
		return fmt.Errorf("failed to remove track from playlist: %w", err)
	}

	s.logger.Info("Track removed from playlist",
		zap.String("playlist_id", playlistID.String()),
		zap.String("track_id", trackID.String()),
		zap.String("user_id", userID.String()),
	)

	return nil
}

// RemoveTrack est un alias pour RemoveTrackFromPlaylist (compatibilité)
// MIGRATION UUID: userID en uuid.UUID, playlistID et trackID en uuid.UUID
func (s *PlaylistService) RemoveTrack(ctx context.Context, playlistID, trackID uuid.UUID, userID uuid.UUID) error {
	return s.RemoveTrackFromPlaylist(ctx, playlistID, trackID, userID)
}

// ReorderPlaylistTracks réorganise les tracks d'une playlist
// T0466: Implémentation avec PlaylistTrackRepository
// trackPositions est une map de trackID -> position
// MIGRATION UUID: userID en uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistService) ReorderPlaylistTracks(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID, trackPositions map[uuid.UUID]int) error {
	// Vérifier ownership
	playlist, err := s.playlistRepo.GetByID(ctx, playlistID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return ErrPlaylistNotFound
		}
		return fmt.Errorf("failed to check playlist: %w", err)
	}

	if playlist.UserID != userID {
		return errors.New("forbidden")
	}

	// Réorganiser les tracks via le repository
	if err := s.playlistTrackRepo.ReorderTracks(ctx, playlistID, trackPositions); err != nil {
		return fmt.Errorf("failed to reorder tracks: %w", err)
	}

	s.logger.Info("Playlist tracks reordered",
		zap.String("playlist_id", playlistID.String()),
		zap.String("user_id", userID.String()),
		zap.Int("tracks_count", len(trackPositions)),
	)

	return nil
}

// ReorderTracks est un alias pour ReorderPlaylistTracks (compatibilité)
// trackIDs est une liste de trackIDs dans l'ordre souhaité (position = index + 1)
// MIGRATION UUID: userID en uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistService) ReorderTracks(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID, trackIDs []uuid.UUID) error {
	trackPositions := make(map[uuid.UUID]int)
	for i, trackID := range trackIDs {
		trackPositions[trackID] = i + 1
	}
	return s.ReorderPlaylistTracks(ctx, playlistID, userID, trackPositions)
}

// AddCollaborator ajoute un collaborateur à une playlist
// T0478: Implémentation avec vérification d'ownership
// MIGRATION UUID: ownerID et collaboratorUserID migrés vers uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistService) AddCollaborator(ctx context.Context, playlistID uuid.UUID, ownerID, collaboratorUserID uuid.UUID, permission models.PlaylistPermission) (*models.PlaylistCollaborator, error) {
	// Vérifier ownership
	playlist, err := s.playlistRepo.GetByID(ctx, playlistID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrPlaylistNotFound
		}
		return nil, fmt.Errorf("failed to check playlist: %w", err)
	}

	if playlist.UserID != ownerID {
		return nil, errors.New("forbidden: only playlist owner can add collaborators")
	}

	// Vérifier que l'utilisateur collaborateur existe
	if gormRepo, ok := s.userRepo.(interface {
		Exists(ctx context.Context, userID uuid.UUID) (bool, error)
	}); ok {
		exists, err := gormRepo.Exists(ctx, collaboratorUserID)
		if err != nil {
			return nil, fmt.Errorf("failed to check user: %w", err)
		}
		if !exists {
			return nil, errors.New("user not found")
		}
	} else {
		_, err := s.userRepo.GetByID(collaboratorUserID.String())
		if err != nil {
			return nil, errors.New("user not found")
		}
	}

	// Vérifier qu'on n'ajoute pas le propriétaire comme collaborateur
	if collaboratorUserID == ownerID {
		return nil, errors.New("cannot add playlist owner as collaborator")
	}

	// Ajouter le collaborateur via le repository
	collaborator, err := s.playlistCollaboratorRepo.AddCollaborator(ctx, playlistID, collaboratorUserID, permission)
	if err != nil {
		if err.Error() == "collaborator already exists" {
			return nil, errors.New("user is already a collaborator")
		}
		return nil, fmt.Errorf("failed to add collaborator: %w", err)
	}

	s.logger.Info("Collaborator added to playlist",
		zap.String("playlist_id", playlistID.String()),
		zap.String("owner_id", ownerID.String()),
		zap.String("collaborator_user_id", collaboratorUserID.String()),
		zap.String("permission", string(permission)),
	)

	// T0508: Envoyer une notification au collaborateur
	if s.playlistNotificationService != nil {
		if err := s.playlistNotificationService.NotifyCollaboratorAdded(ctx, playlistID, collaboratorUserID, ownerID); err != nil {
			s.logger.Warn("Failed to send collaborator added notification", zap.Error(err))
		}
	}

	return collaborator, nil
}

// RemoveCollaborator retire un collaborateur d'une playlist
// T0478: Implémentation avec vérification d'ownership
// MIGRATION UUID: ownerID et collaboratorUserID migrés vers uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistService) RemoveCollaborator(ctx context.Context, playlistID uuid.UUID, ownerID, collaboratorUserID uuid.UUID) error {
	// Vérifier ownership
	playlist, err := s.playlistRepo.GetByID(ctx, playlistID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return ErrPlaylistNotFound
		}
		return fmt.Errorf("failed to check playlist: %w", err)
	}

	if playlist.UserID != ownerID {
		return errors.New("forbidden: only playlist owner can remove collaborators")
	}

	// Retirer le collaborateur via le repository
	if err := s.playlistCollaboratorRepo.RemoveCollaborator(ctx, playlistID, collaboratorUserID); err != nil {
		if err == gorm.ErrRecordNotFound {
			return errors.New("collaborator not found")
		}
		return fmt.Errorf("failed to remove collaborator: %w", err)
	}

	s.logger.Info("Collaborator removed from playlist",
		zap.String("playlist_id", playlistID.String()),
		zap.String("owner_id", ownerID.String()),
		zap.String("collaborator_user_id", collaboratorUserID.String()),
	)

	return nil
}

// UpdateCollaboratorPermission met à jour la permission d'un collaborateur
// T0478: Implémentation avec vérification d'ownership
// MIGRATION UUID: ownerID et collaboratorUserID migrés vers uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistService) UpdateCollaboratorPermission(ctx context.Context, playlistID uuid.UUID, ownerID, collaboratorUserID uuid.UUID, permission models.PlaylistPermission) error {
	// Vérifier ownership
	playlist, err := s.playlistRepo.GetByID(ctx, playlistID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return ErrPlaylistNotFound
		}
		return fmt.Errorf("failed to check playlist: %w", err)
	}

	if playlist.UserID != ownerID {
		return errors.New("forbidden: only playlist owner can update collaborator permissions")
	}

	// Valider la permission
	if !permission.IsValid() {
		return errors.New("invalid permission")
	}

	// Mettre à jour la permission via le repository
	if err := s.playlistCollaboratorRepo.UpdatePermission(ctx, playlistID, collaboratorUserID, permission); err != nil {
		if err == gorm.ErrRecordNotFound {
			return errors.New("collaborator not found")
		}
		return fmt.Errorf("failed to update collaborator permission: %w", err)
	}

	s.logger.Info("Collaborator permission updated",
		zap.String("playlist_id", playlistID.String()),
		zap.String("owner_id", ownerID.String()),
		zap.String("collaborator_user_id", collaboratorUserID.String()),
		zap.String("permission", string(permission)),
	)

	return nil
}

// CheckPermission vérifie si un utilisateur a une certaine permission sur une playlist
// T0478: Vérifie les permissions (read, write, admin)
// MIGRATION UUID: userID en uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistService) CheckPermission(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID, requiredPermission models.PlaylistPermission) (bool, error) {
	// Récupérer la playlist
	playlist, err := s.playlistRepo.GetByID(ctx, playlistID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return false, errors.New("playlist not found")
		}
		return false, fmt.Errorf("failed to check playlist: %w", err)
	}

	// Le propriétaire a toujours toutes les permissions
	if playlist.UserID == userID {
		return true, nil
	}

	// Si la playlist est publique, tout le monde peut la lire
	if playlist.IsPublic && requiredPermission == models.PlaylistPermissionRead {
		return true, nil
	}

	// Vérifier si l'utilisateur est collaborateur
	collaborator, err := s.playlistCollaboratorRepo.GetCollaborator(ctx, playlistID, userID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return false, nil // Pas de permission
		}
		return false, fmt.Errorf("failed to check collaborator: %w", err)
	}

	// Vérifier la permission selon le niveau requis
	switch requiredPermission {
	case models.PlaylistPermissionRead:
		return collaborator.CanRead(), nil
	case models.PlaylistPermissionWrite:
		return collaborator.CanWrite(), nil
	case models.PlaylistPermissionAdmin:
		return collaborator.CanAdmin(), nil
	default:
		return false, errors.New("invalid permission")
	}
}

// GetCollaborators récupère tous les collaborateurs d'une playlist
// T0478: Helper method pour récupérer les collaborateurs
// MIGRATION UUID: userID en uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistService) GetCollaborators(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID) ([]*models.PlaylistCollaborator, error) {
	// Vérifier que l'utilisateur a accès à la playlist (propriétaire ou collaborateur)
	hasAccess, err := s.CheckPermission(ctx, playlistID, userID, models.PlaylistPermissionRead)
	if err != nil {
		return nil, err
	}
	if !hasAccess {
		return nil, errors.New("forbidden: access denied")
	}

	// Récupérer les collaborateurs
	collaborators, err := s.playlistCollaboratorRepo.GetCollaborators(ctx, playlistID)
	if err != nil {
		return nil, fmt.Errorf("failed to get collaborators: %w", err)
	}

	return collaborators, nil
}

// CreateShareLink crée un nouveau lien de partage public pour une playlist
// T0488: Create Playlist Public Share Link
// MIGRATION UUID: userID en uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistService) CreateShareLink(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID, expiresAt *time.Time) (*models.PlaylistShareLink, error) {
	if s.playlistShareService == nil {
		return nil, errors.New("playlist share service not initialized")
	}

	// Vérifier que l'utilisateur a la permission (owner ou admin)
	hasPermission, err := s.CheckPermission(ctx, playlistID, userID, models.PlaylistPermissionAdmin)
	if err != nil {
		return nil, err
	}
	if !hasPermission {
		// Vérifier si l'utilisateur est le propriétaire
		playlist, err := s.GetPlaylist(ctx, playlistID, &userID)
		if err != nil {
			return nil, err
		}
		if playlist.UserID != userID {
			return nil, errors.New("forbidden: only owner or admin can create share links")
		}
	}

	shareLink, err := s.playlistShareService.CreateShareLink(ctx, playlistID, userID, expiresAt)
	if err != nil {
		return nil, err
	}

	// T0508: Envoyer une notification
	if s.playlistNotificationService != nil {
		if err := s.playlistNotificationService.NotifyPlaylistShared(ctx, playlistID, userID); err != nil {
			s.logger.Warn("Failed to send playlist shared notification", zap.Error(err))
		}
	}

	return shareLink, nil
}

// FollowPlaylist permet à un utilisateur de suivre une playlist
// T0489: Create Playlist Follow Feature
// MIGRATION UUID: userID en uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistService) FollowPlaylist(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID) error {
	if s.playlistFollowService == nil {
		return errors.New("playlist follow service not initialized")
	}
	return s.playlistFollowService.FollowPlaylist(ctx, userID, playlistID)
}

// UnfollowPlaylist permet à un utilisateur de ne plus suivre une playlist
// T0489: Create Playlist Follow Feature
// MIGRATION UUID: userID en uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistService) UnfollowPlaylist(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID) error {
	if s.playlistFollowService == nil {
		return errors.New("playlist follow service not initialized")
	}
	return s.playlistFollowService.UnfollowPlaylist(ctx, userID, playlistID)
}

// IsFollowing vérifie si un utilisateur suit une playlist
// T0489: Create Playlist Follow Feature
// MIGRATION UUID: userID en uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistService) IsFollowing(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID) (bool, error) {
	if s.playlistFollowService == nil {
		return false, errors.New("playlist follow service not initialized")
	}
	return s.playlistFollowService.IsFollowing(ctx, userID, playlistID)
}
