package handlers

import (
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"time"

	apperrors "veza-backend-api/internal/errors"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/monitoring"
	"veza-backend-api/internal/services"
	"veza-backend-api/internal/utils"

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

// PlaylistHandler gère les opérations sur les playlists
type PlaylistHandler struct {
	playlistService          services.PlaylistServiceInterface
	playlistAnalyticsService *services.PlaylistAnalyticsService
	playlistFollowService    *services.PlaylistFollowService
	db                       *gorm.DB
	commonHandler            *CommonHandler
}

// NewPlaylistHandler crée un nouveau handler de playlists
func NewPlaylistHandler(playlistService *services.PlaylistService, db *gorm.DB, logger *zap.Logger) *PlaylistHandler {
	return &PlaylistHandler{
		playlistService: playlistService,
		db:              db,
		commonHandler:   NewCommonHandler(logger),
	}
}

// NewPlaylistHandlerWithInterface crée un nouveau handler avec l'interface service (pour les tests)
func NewPlaylistHandlerWithInterface(playlistService services.PlaylistServiceInterface, db *gorm.DB, logger *zap.Logger) *PlaylistHandler {
	return &PlaylistHandler{
		playlistService: playlistService,
		db:              db,
		commonHandler:   NewCommonHandler(logger),
	}
}

// SetPlaylistAnalyticsService définit le service d'analytics de playlist
// T0491: Create Playlist Analytics Backend
func (h *PlaylistHandler) SetPlaylistAnalyticsService(analyticsService *services.PlaylistAnalyticsService) {
	h.playlistAnalyticsService = analyticsService
}

// SetPlaylistFollowService définit le service de follow de playlist
// T0498: Create Playlist Recommendations
func (h *PlaylistHandler) SetPlaylistFollowService(followService *services.PlaylistFollowService) {
	h.playlistFollowService = followService
}

// CreatePlaylistRequest représente la requête pour créer une playlist
// MOD-P1-001: Ajout tags validate pour validation systématique (Description manquait)
type CreatePlaylistRequest struct {
	Title       string `json:"title" binding:"required,min=1,max=200" validate:"required,min=1,max=200"`
	Description string `json:"description,omitempty" validate:"omitempty,max=1000"`
	IsPublic    bool   `json:"is_public"`
}

// UpdatePlaylistRequest représente la requête pour mettre à jour une playlist
// MOD-P1-001: Ajout tags validate pour validation systématique (Description manquait)
type UpdatePlaylistRequest struct {
	Title       *string `json:"title,omitempty" binding:"omitempty,min=1,max=200" validate:"omitempty,min=1,max=200"`
	Description *string `json:"description,omitempty" validate:"omitempty,max=1000"`
	IsPublic    *bool   `json:"is_public,omitempty"`
}

// ReorderTracksRequest représente la requête pour réorganiser les tracks
type ReorderTracksRequest struct {
	TrackIDs []uuid.UUID `json:"track_ids" binding:"required,min=1" validate:"required,min=1"` // Changed to []uuid.UUID
}

// CreatePlaylist gère la création d'une playlist
// @Summary      Create Playlist
// @Description  Create a new playlist
// @Tags         Playlist
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        request body CreatePlaylistRequest true "Playlist Metadata"
// @Success      201  {object}  APIResponse{data=object{playlist=models.Playlist}}
// @Failure      400  {object}  APIResponse "Validation Error"
// @Failure      401  {object}  APIResponse "Unauthorized"
// @Failure      500  {object}  APIResponse "Internal Error"
// @Router       /playlists [post]
func (h *PlaylistHandler) CreatePlaylist(c *gin.Context) {
	// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
	userID, ok := GetUserIDUUID(c)
	if !ok {
		return // Erreur déjà envoyée par GetUserIDUUID
	}

	var req CreatePlaylistRequest
	if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
		RespondWithAppError(c, appErr)
		return
	}

	// BE-SEC-009: Sanitize user inputs to prevent XSS and injection attacks
	req.Title = utils.SanitizeText(req.Title, 200)
	if req.Description != "" {
		req.Description = utils.SanitizeText(req.Description, 1000)
	}

	// MOD-P1-004: Ajouter timeout context pour opération DB critique
	ctx, cancel := WithTimeout(c.Request.Context(), 5*time.Second)
	defer cancel()

	playlist, err := h.playlistService.CreatePlaylist(ctx, userID, req.Title, req.Description, req.IsPublic)
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to create playlist", err))
		return
	}

	// MOD-P2-003: Enregistrer la métrique business (depuis le handler pour éviter cycle d'import)
	monitoring.RecordPlaylistCreated()

	RespondSuccess(c, http.StatusCreated, playlist)
}

// GetPlaylists gère la récupération des playlists avec pagination
// @Summary      Get Playlists
// @Description  Get a paginated list of playlists
// @Tags         Playlist
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        page     query     int     false  "Page number" default(1)
// @Param        limit    query     int     false  "Items per page" default(20)
// @Param        user_id  query     string  false  "Filter by User ID"
// @Success      200  {object}  APIResponse{data=object{playlists=[]models.Playlist,pagination=object}}
// @Failure      500  {object}  APIResponse "Internal Error"
// @Router       /playlists [get]
func (h *PlaylistHandler) GetPlaylists(c *gin.Context) {
	page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
	limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))

	if page < 1 {
		page = 1
	}
	if limit < 1 {
		limit = 20
	}
	if limit > 100 {
		limit = 100
	}

	// Filtres optionnels
	var filterUserID *uuid.UUID
	if filterUserIDStr := c.Query("user_id"); filterUserIDStr != "" {
		if uid, err := uuid.Parse(filterUserIDStr); err == nil {
			filterUserID = &uid
		}
	}

	// Get current user ID
	var currentUserID *uuid.UUID
	if uidInterface, exists := c.Get("user_id"); exists {
		fmt.Printf("🔍 [HANDLER] GetPlaylists: user_id found in context: %v (Type: %T)\n", uidInterface, uidInterface)
		if uid, ok := uidInterface.(uuid.UUID); ok {
			currentUserID = &uid
		} else {
			fmt.Printf("❌ [HANDLER] GetPlaylists: user_id type assertion failed\n")
		}
	} else {
		fmt.Printf("❌ [HANDLER] GetPlaylists: user_id NOT found in context\n")
	}

	// MOD-P1-004: Ajouter timeout context pour opération DB
	ctx, cancel := WithTimeout(c.Request.Context(), 5*time.Second)
	defer cancel()
	playlists, total, err := h.playlistService.GetPlaylists(ctx, currentUserID, filterUserID, page, limit)
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get playlists", err))
		return
	}

	RespondSuccess(c, http.StatusOK, gin.H{
		"playlists": playlists,
		"total":     total,
		"page":      page,
		"limit":     limit,
	})
}

// GetPlaylist gère la récupération d'une playlist
// @Summary      Get Playlist by ID
// @Description  Get detailed information about a playlist
// @Tags         Playlist
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id   path      string  true  "Playlist ID"
// @Success      200  {object}  APIResponse{data=object{playlist=models.Playlist}}
// @Failure      400  {object}  APIResponse "Invalid ID"
// @Failure      404  {object}  APIResponse "Playlist not found"
// @Router       /playlists/{id} [get]
func (h *PlaylistHandler) GetPlaylist(c *gin.Context) {
	// Playlist IDs are uuid.UUID
	playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
		return
	}

	var currentUserID *uuid.UUID
	if uidInterface, exists := c.Get("user_id"); exists {
		if uid, ok := uidInterface.(uuid.UUID); ok {
			currentUserID = &uid
		}
	}

	// MOD-P1-004: Ajouter timeout context pour opération DB
	ctx, cancel := WithTimeout(c.Request.Context(), 5*time.Second)
	defer cancel()
	playlist, err := h.playlistService.GetPlaylist(ctx, playlistID, currentUserID)
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if errors.Is(err, services.ErrPlaylistNotFound) || errors.Is(err, services.ErrAccessDenied) {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get playlist", err))
		return
	}

	RespondSuccess(c, http.StatusOK, playlist)
}

// UpdatePlaylist gère la mise à jour d'une playlist
// @Summary      Update Playlist
// @Description  Update playlist metadata
// @Tags         Playlist
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id        path      string                 true  "Playlist ID"
// @Param        playlist  body      UpdatePlaylistRequest  true  "Playlist Metadata"
// @Success      200  {object}  APIResponse{data=object{playlist=models.Playlist}}
// @Failure      400  {object}  APIResponse "Validation Error"
// @Failure      401  {object}  APIResponse "Unauthorized"
// @Failure      403  {object}  APIResponse "Forbidden"
// @Failure      404  {object}  APIResponse "Playlist not found"
// @Router       /playlists/{id} [put]
func (h *PlaylistHandler) UpdatePlaylist(c *gin.Context) {
	// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
	userID, ok := GetUserIDUUID(c)
	if !ok {
		return // Erreur déjà envoyée par GetUserIDUUID
	}

	// Playlist IDs are uuid.UUID
	playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
		return
	}

	var req UpdatePlaylistRequest
	if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
		RespondWithAppError(c, appErr)
		return
	}

	// BE-SEC-009: Sanitize user inputs to prevent XSS and injection attacks
	if req.Title != nil {
		sanitized := utils.SanitizeText(*req.Title, 200)
		req.Title = &sanitized
	}
	if req.Description != nil {
		sanitized := utils.SanitizeText(*req.Description, 1000)
		req.Description = &sanitized
	}

	// MOD-P1-004: Ajouter timeout context pour opération DB
	ctx, cancel := WithTimeout(c.Request.Context(), 5*time.Second)
	defer cancel()
	playlist, err := h.playlistService.UpdatePlaylist(ctx, playlistID, userID, req.Title, req.Description, req.IsPublic)
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if errors.Is(err, services.ErrPlaylistNotFound) {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		if errors.Is(err, services.ErrAccessDenied) {
			RespondWithAppError(c, apperrors.NewForbiddenError("forbidden"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to update playlist", err))
		return
	}

	RespondSuccess(c, http.StatusOK, playlist)
}

// DeletePlaylist gère la suppression d'une playlist
// @Summary      Delete Playlist
// @Description  Permanently delete a playlist
// @Tags         Playlist
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id   path      string  true  "Playlist ID"
// @Success      200  {object}  APIResponse{data=object{message=string}}
// @Failure      401  {object}  APIResponse "Unauthorized"
// @Failure      403  {object}  APIResponse "Forbidden"
// @Failure      404  {object}  APIResponse "Playlist not found"
// @Router       /playlists/{id} [delete]
func (h *PlaylistHandler) DeletePlaylist(c *gin.Context) {
	// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
	userID, ok := GetUserIDUUID(c)
	if !ok {
		return // Erreur déjà envoyée par GetUserIDUUID
	}

	// Playlist IDs are uuid.UUID
	playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
		return
	}

	// MOD-P1-004: Ajouter timeout context pour opération DB
	ctx, cancel := WithTimeout(c.Request.Context(), 5*time.Second)
	defer cancel()
	if err := h.playlistService.DeletePlaylist(ctx, playlistID, userID); err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if errors.Is(err, services.ErrPlaylistNotFound) {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		if errors.Is(err, services.ErrAccessDenied) {
			RespondWithAppError(c, apperrors.NewForbiddenError("forbidden"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to delete playlist", err))
		return
	}

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

// AddTrack gère l'ajout d'un track à une playlist
// @Summary      Add Track to Playlist
// @Description  Add a track to the playlist
// @Tags         Playlist
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id       path      string  true  "Playlist ID"
// @Param        trackId  body      object{track_id=string} true  "Track ID (in body)"
// @Success      200  {object}  APIResponse{data=object{message=string}}
// @Failure      400  {object}  APIResponse "Track already present or invalid ID"
// @Failure      404  {object}  APIResponse "Playlist or Track not found"
// @Router       /playlists/{id}/tracks [post]
func (h *PlaylistHandler) AddTrack(c *gin.Context) {
	// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
	userID, ok := GetUserIDUUID(c)
	if !ok {
		return // Erreur déjà envoyée par GetUserIDUUID
	}

	// Playlist IDs are uuid.UUID
	playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
		return
	}

	// Track IDs are uuid.UUID
	trackID, err := uuid.Parse(c.Param("trackId")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid track id"))
		return
	}

	if err := h.playlistService.AddTrack(c.Request.Context(), playlistID, trackID, userID); err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if errors.Is(err, services.ErrPlaylistNotFound) {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		if errors.Is(err, services.ErrTrackNotFound) {
			RespondWithAppError(c, apperrors.NewNotFoundError("track"))
			return
		}
		if errors.Is(err, services.ErrTrackAlreadyInPlaylist) {
			RespondWithAppError(c, apperrors.NewValidationError("track already in playlist"))
			return
		}
		if errors.Is(err, services.ErrAccessDenied) {
			RespondWithAppError(c, apperrors.NewForbiddenError("forbidden"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to add track to playlist", err))
		return
	}

	RespondSuccess(c, http.StatusOK, gin.H{"message": "track added to playlist"})
}

// RemoveTrack gère la suppression d'un track d'une playlist
// @Summary      Remove Track from Playlist
// @Description  Remove a track from the playlist
// @Tags         Playlist
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id       path      string  true  "Playlist ID"
// @Param        trackId  path      string  true  "Track ID"
// @Success      200  {object}  APIResponse{data=object{message=string}}
// @Failure      404  {object}  APIResponse "Playlist or Track not found"
// @Router       /playlists/{id}/tracks/{trackId} [delete]
func (h *PlaylistHandler) RemoveTrack(c *gin.Context) {
	// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
	userID, ok := GetUserIDUUID(c)
	if !ok {
		return // Erreur déjà envoyée par GetUserIDUUID
	}

	// Playlist IDs are uuid.UUID
	playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
		return
	}

	// Track IDs are uuid.UUID
	trackID, err := uuid.Parse(c.Param("trackId")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid track id"))
		return
	}

	if err := h.playlistService.RemoveTrack(c.Request.Context(), playlistID, trackID, userID); err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if err.Error() == "playlist not found" {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		if err.Error() == "track not in playlist" {
			RespondWithAppError(c, apperrors.NewNotFoundError("track not in playlist"))
			return
		}
		if err.Error() == "forbidden" {
			RespondWithAppError(c, apperrors.NewForbiddenError("forbidden"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to remove track from playlist", err))
		return
	}

	RespondSuccess(c, http.StatusOK, gin.H{"message": "track removed from playlist"})
}

// ReorderTracks gère la réorganisation des tracks d'une playlist
// @Summary      Reorder Tracks
// @Description  Reorder tracks in the playlist
// @Tags         Playlist
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id       path      string                true  "Playlist ID"
// @Param        order    body      ReorderTracksRequest  true  "New Track Order"
// @Success      200  {object}  APIResponse{data=object{message=string}}
// @Failure      400  {object}  APIResponse "Validation Error"
// @Router       /playlists/{id}/tracks/reorder [put]
func (h *PlaylistHandler) ReorderTracks(c *gin.Context) {
	// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
	userID, ok := GetUserIDUUID(c)
	if !ok {
		return // Erreur déjà envoyée par GetUserIDUUID
	}

	// Playlist IDs are uuid.UUID
	playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
		return
	}

	var req ReorderTracksRequest
	if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
		RespondWithAppError(c, appErr)
		return
	}

	if err := h.playlistService.ReorderTracks(c.Request.Context(), playlistID, userID, req.TrackIDs); err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if err.Error() == "playlist not found" {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		if err.Error() == "some tracks are not in the playlist" {
			RespondWithAppError(c, apperrors.NewValidationError("some tracks are not in the playlist"))
			return
		}
		if err.Error() == "forbidden" {
			RespondWithAppError(c, apperrors.NewForbiddenError("forbidden"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to reorder tracks", err))
		return
	}

	RespondSuccess(c, http.StatusOK, gin.H{"message": "tracks reordered"})
}

// AddCollaboratorRequest représente la requête pour ajouter un collaborateur
type AddCollaboratorRequest struct {
	UserID     uuid.UUID `json:"user_id" binding:"required" validate:"required"`
	Permission string    `json:"permission" binding:"required,oneof=read write admin" validate:"required,oneof=read write admin"`
}

// UpdateCollaboratorPermissionRequest représente la requête pour mettre à jour la permission d'un collaborateur
type UpdateCollaboratorPermissionRequest struct {
	Permission string `json:"permission" binding:"required,oneof=read write admin" validate:"required,oneof=read write admin"`
}

// AddCollaborator gère l'ajout d'un collaborateur à une playlist
// T0479: POST /api/v1/playlists/:id/collaborators
func (h *PlaylistHandler) AddCollaborator(c *gin.Context) {
	// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
	userID, ok := GetUserIDUUID(c)
	if !ok {
		return // Erreur déjà envoyée par GetUserIDUUID
	}

	// Playlist IDs are uuid.UUID
	playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
		return
	}

	var req AddCollaboratorRequest
	if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
		RespondWithAppError(c, appErr)
		return
	}

	// Convertir la permission string en PlaylistPermission
	var permission models.PlaylistPermission
	switch req.Permission {
	case "read":
		permission = models.PlaylistPermissionRead
	case "write":
		permission = models.PlaylistPermissionWrite
	case "admin":
		permission = models.PlaylistPermissionAdmin
	default:
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid permission"))
		return
	}

	collaborator, err := h.playlistService.AddCollaborator(c.Request.Context(), playlistID, userID, req.UserID, permission)
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if err.Error() == "playlist not found" {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		if err.Error() == "user not found" {
			RespondWithAppError(c, apperrors.NewNotFoundError("user"))
			return
		}
		if err.Error() == "user is already a collaborator" {
			RespondWithAppError(c, apperrors.New(apperrors.ErrCodeConflict, "user is already a collaborator"))
			return
		}
		if err.Error() == "cannot add playlist owner as collaborator" {
			RespondWithAppError(c, apperrors.NewValidationError("cannot add playlist owner as collaborator"))
			return
		}
		if err.Error() == "forbidden: only playlist owner can add collaborators" {
			RespondWithAppError(c, apperrors.NewForbiddenError("forbidden"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to add collaborator", err))
		return
	}

	RespondSuccess(c, http.StatusCreated, collaborator)
}

// RemoveCollaborator gère la suppression d'un collaborateur d'une playlist
// T0479: DELETE /api/v1/playlists/:id/collaborators/:userId
func (h *PlaylistHandler) RemoveCollaborator(c *gin.Context) {
	// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
	userID, ok := GetUserIDUUID(c)
	if !ok {
		return // Erreur déjà envoyée par GetUserIDUUID
	}

	// Playlist IDs are uuid.UUID
	playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
	if err != nil {
		RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
		return
	}

	// User IDs are UUID
	collaboratorUserID, err := uuid.Parse(c.Param("userId"))
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid user id"))
		return
	}

	if err := h.playlistService.RemoveCollaborator(c.Request.Context(), playlistID, userID, collaboratorUserID); err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if err.Error() == "playlist not found" {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		if err.Error() == "collaborator not found" {
			RespondWithAppError(c, apperrors.NewNotFoundError("collaborator"))
			return
		}
		if err.Error() == "forbidden: only playlist owner can remove collaborators" {
			RespondWithAppError(c, apperrors.NewForbiddenError("forbidden"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to remove collaborator", err))
		return
	}

	RespondSuccess(c, http.StatusOK, gin.H{"message": "collaborator removed"})
}

// UpdateCollaboratorPermission gère la mise à jour de la permission d'un collaborateur
// T0479: PUT /api/v1/playlists/:id/collaborators/:userId
func (h *PlaylistHandler) UpdateCollaboratorPermission(c *gin.Context) {
	// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
	userID, ok := GetUserIDUUID(c)
	if !ok {
		return // Erreur déjà envoyée par GetUserIDUUID
	}

	// Playlist IDs are uuid.UUID
	playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
		return
	}

	// User IDs are UUID
	collaboratorUserID, err := uuid.Parse(c.Param("userId"))
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid user id"))
		return
	}

	var req UpdateCollaboratorPermissionRequest
	if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
		RespondWithAppError(c, appErr)
		return
	}

	// Convertir la permission string en PlaylistPermission
	var permission models.PlaylistPermission
	switch req.Permission {
	case "read":
		permission = models.PlaylistPermissionRead
	case "write":
		permission = models.PlaylistPermissionWrite
	case "admin":
		permission = models.PlaylistPermissionAdmin
	default:
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid permission"))
		return
	}

	if err := h.playlistService.UpdateCollaboratorPermission(c.Request.Context(), playlistID, userID, collaboratorUserID, permission); err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if err.Error() == "playlist not found" {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		if err.Error() == "collaborator not found" {
			RespondWithAppError(c, apperrors.NewNotFoundError("collaborator"))
			return
		}
		if err.Error() == "invalid permission" {
			RespondWithAppError(c, apperrors.NewValidationError("invalid permission"))
			return
		}
		if err.Error() == "forbidden: only playlist owner can update collaborator permissions" {
			RespondWithAppError(c, apperrors.NewForbiddenError("forbidden"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to update collaborator permission", err))
		return
	}

	RespondSuccess(c, http.StatusOK, gin.H{"message": "collaborator permission updated"})
}

// GetCollaborators gère la récupération des collaborateurs d'une playlist
// T0479: GET /api/v1/playlists/:id/collaborators
func (h *PlaylistHandler) GetCollaborators(c *gin.Context) {
	// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
	userID, ok := GetUserIDUUID(c)
	if !ok {
		return // Erreur déjà envoyée par GetUserIDUUID
	}

	// Playlist IDs are uuid.UUID
	playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
		return
	}

	collaborators, err := h.playlistService.GetCollaborators(c.Request.Context(), playlistID, userID)
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if err.Error() == "playlist not found" {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		if err.Error() == "forbidden: access denied" {
			RespondWithAppError(c, apperrors.NewForbiddenError("forbidden"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get collaborators", err))
		return
	}

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

// CreateShareLink gère la création d'un lien de partage public pour une playlist
// T0488: Create Playlist Public Share Link
func (h *PlaylistHandler) CreateShareLink(c *gin.Context) {
	// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
	userID, ok := GetUserIDUUID(c)
	if !ok {
		return // Erreur déjà envoyée par GetUserIDUUID
	}

	// Playlist IDs are uuid.UUID
	playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
		return
	}

	// Créer le lien de partage via le service
	// La vérification des permissions (owner ou admin) est faite dans PlaylistService.CreateShareLink
	shareLink, err := h.playlistService.CreateShareLink(c.Request.Context(), playlistID, userID, nil)
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if err.Error() == "playlist not found" {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		if err.Error() == "forbidden: only owner or admin can create share links" {
			RespondWithAppError(c, apperrors.NewForbiddenError("forbidden"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to create share link", err))
		return
	}

	RespondSuccess(c, http.StatusOK, shareLink)
}

// FollowPlaylist gère le follow d'une playlist
// T0489: Create Playlist Follow Feature
func (h *PlaylistHandler) FollowPlaylist(c *gin.Context) {
	// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
	userID, ok := GetUserIDUUID(c)
	if !ok {
		return // Erreur déjà envoyée par GetUserIDUUID
	}

	// Playlist IDs are uuid.UUID
	playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
		return
	}

	err = h.playlistService.FollowPlaylist(c.Request.Context(), playlistID, userID)
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if err.Error() == "playlist not found" {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		if err.Error() == "cannot follow own playlist" {
			RespondWithAppError(c, apperrors.NewValidationError("cannot follow own playlist"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to follow playlist", err))
		return
	}

	RespondSuccess(c, http.StatusOK, gin.H{"message": "playlist followed"})
}

// UnfollowPlaylist gère l'unfollow d'une playlist
// T0489: Create Playlist Follow Feature
func (h *PlaylistHandler) UnfollowPlaylist(c *gin.Context) {
	// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
	userID, ok := GetUserIDUUID(c)
	if !ok {
		return // Erreur déjà envoyée par GetUserIDUUID
	}

	// Playlist IDs are uuid.UUID
	playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
		return
	}

	err = h.playlistService.UnfollowPlaylist(c.Request.Context(), playlistID, userID)
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if err.Error() == "playlist not found" {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to unfollow playlist", err))
		return
	}

	RespondSuccess(c, http.StatusOK, gin.H{"message": "playlist unfollowed"})
}

// GetPlaylistStats gère la récupération des statistiques d'une playlist
// T0491: Create Playlist Analytics Backend
func (h *PlaylistHandler) GetPlaylistStats(c *gin.Context) {
	// Playlist IDs are uuid.UUID
	playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
		return
	}

	// Vérifier que la playlist existe et que l'utilisateur a accès
	var userID *uuid.UUID
	if uidInterface, exists := c.Get("user_id"); exists {
		if uid, ok := uidInterface.(uuid.UUID); ok {
			userID = &uid
		}
	}

	playlist, err := h.playlistService.GetPlaylist(c.Request.Context(), playlistID, userID)
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if err.Error() == "playlist not found" {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get playlist", err))
		return
	}

	// Vérifier que l'utilisateur a accès (propriétaire, collaborateur ou playlist publique)
	// Use uuid.Nil for comparison if userID is nil
	currentUserID := uuid.Nil
	if userID != nil {
		currentUserID = *userID
	}

	if playlist.UserID != currentUserID && !playlist.IsPublic {
		// Vérifier si l'utilisateur est collaborateur
		if userID != nil {
			hasAccess, err := h.playlistService.CheckPermission(c.Request.Context(), playlistID, *userID, models.PlaylistPermissionRead)
			if err != nil || !hasAccess {
				// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
				RespondWithAppError(c, apperrors.NewForbiddenError("forbidden"))
				return
			}
		} else {
			// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
			RespondWithAppError(c, apperrors.NewForbiddenError("forbidden"))
			return
		}
	}

	// Récupérer les statistiques via le service d'analytics
	if h.playlistAnalyticsService == nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.New(apperrors.ErrCodeInternal, "analytics service not available"))
		return
	}

	stats, err := h.playlistAnalyticsService.GetPlaylistStats(c.Request.Context(), playlistID)
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if err.Error() == "playlist not found" {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get playlist stats", err))
		return
	}

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

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

// DuplicatePlaylist gère la duplication d'une playlist
// T0495: Create Playlist Duplicate Feature
func (h *PlaylistHandler) DuplicatePlaylist(c *gin.Context) {
	// Playlist IDs are uuid.UUID
	playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
		return
	}

	// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
	userID, ok := GetUserIDUUID(c)
	if !ok {
		return // Erreur déjà envoyée par GetUserIDUUID
	}

	var req DuplicatePlaylistRequest
	if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
		RespondWithAppError(c, appErr)
		return
	}

	// Créer le service de duplication
	duplicateService := services.NewPlaylistDuplicateService(h.playlistService, h.db, nil)

	// Dupliquer la playlist
	newPlaylist, err := duplicateService.DuplicatePlaylist(
		c.Request.Context(),
		playlistID,
		userID,
		services.DuplicatePlaylistRequest{
			NewTitle:       req.NewTitle,
			NewDescription: req.NewDescription,
			IsPublic:       req.IsPublic,
		},
	)
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		if err.Error() == "playlist not found" {
			RespondWithAppError(c, apperrors.NewNotFoundError("playlist"))
			return
		}
		if err.Error() == "forbidden: you don't have access to this playlist" {
			RespondWithAppError(c, apperrors.NewForbiddenError("forbidden"))
			return
		}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to duplicate playlist", err))
		return
	}

	RespondSuccess(c, http.StatusOK, gin.H{
		"message":  "playlist duplicated successfully",
		"playlist": newPlaylist,
	})
}

// SearchPlaylists gère la recherche de playlists
// T0496: Create Playlist Search Backend
func (h *PlaylistHandler) SearchPlaylists(c *gin.Context) {
	// Get current user ID
	var currentUserID *uuid.UUID
	if uidInterface, exists := c.Get("user_id"); exists {
		if uid, ok := uidInterface.(uuid.UUID); ok {
			currentUserID = &uid
		}
	}

	// Récupérer les paramètres de recherche
	query := c.Query("q")
	userIDParam := c.Query("user_id")
	isPublicParam := c.Query("is_public")
	pageParam := c.DefaultQuery("page", "1")
	limitParam := c.DefaultQuery("limit", "20")

	// Parser les paramètres
	var filterUserID *uuid.UUID
	if userIDParam != "" {
		if parsed, err := uuid.Parse(userIDParam); err == nil {
			filterUserID = &parsed
		}
	}

	var filterIsPublic *bool
	if isPublicParam != "" {
		if parsed, err := strconv.ParseBool(isPublicParam); err == nil {
			filterIsPublic = &parsed
		}
	}

	page, err := strconv.Atoi(pageParam)
	if err != nil || page < 1 {
		page = 1
	}

	limit, err := strconv.Atoi(limitParam)
	if err != nil || limit < 1 {
		limit = 20
	}

	// Rechercher les playlists
	playlists, total, err := h.playlistService.SearchPlaylists(c.Request.Context(), services.SearchPlaylistsParams{
		Query:         query,
		UserID:        filterUserID,
		IsPublic:      filterIsPublic,
		Page:          page,
		Limit:         limit,
		CurrentUserID: currentUserID,
	})
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to search playlists", err))
		return
	}

	RespondSuccess(c, http.StatusOK, gin.H{
		"playlists": playlists,
		"total":     total,
		"page":      page,
		"limit":     limit,
	})
}

// GetRecommendations gère la récupération des recommandations de playlists
// T0498: Create Playlist Recommendations
func (h *PlaylistHandler) GetRecommendations(c *gin.Context) {
	userID, ok := GetUserIDUUID(c)
	if !ok {
		return // Erreur déjà envoyée par GetUserIDUUID
	}
	if userID == uuid.Nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized"))
		return
	}

	// Parser les paramètres de requête
	limitParam := c.DefaultQuery("limit", "20")
	limit, err := strconv.Atoi(limitParam)
	if err != nil || limit < 1 {
		limit = 20
	}
	if limit > 100 {
		limit = 100
	}

	minScoreParam := c.DefaultQuery("min_score", "0.1")
	minScore, err := strconv.ParseFloat(minScoreParam, 64)
	if err != nil || minScore < 0 {
		minScore = 0.1
	}

	includeOwnParam := c.DefaultQuery("include_own", "false")
	includeOwn := includeOwnParam == "true"

	// Vérifier que le service de follow est disponible
	if h.playlistFollowService == nil {
		RespondWithAppError(c, apperrors.New(apperrors.ErrCodeInternal, "follow service not available"))
		return
	}

	// Créer le service de recommandations
	recommendationService := services.NewPlaylistRecommendationService(
		h.db,
		h.playlistService,
		h.playlistFollowService,
		h.commonHandler.logger,
	)

	// Obtenir les recommandations
	recommendations, err := recommendationService.GetRecommendations(
		c.Request.Context(),
		services.GetRecommendationsParams{
			UserID:     userID,
			Limit:      limit,
			MinScore:   minScore,
			IncludeOwn: includeOwn,
		},
	)
	if err != nil {
		// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
		RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get recommendations", err))
		return
	}

	// Formater la réponse
	response := make([]gin.H, 0, len(recommendations))
	for _, rec := range recommendations {
		response = append(response, gin.H{
			"playlist": rec.Playlist,
			"score":    rec.Score,
			"reason":   rec.Reason,
		})
	}

	RespondSuccess(c, http.StatusOK, gin.H{
		"recommendations": response,
		"count":           len(response),
	})
}
