package repositories

import (
	"context"
	"errors"

	"veza-backend-api/internal/models"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

// PlaylistTrackRepository définit l'interface pour les opérations sur les playlist_tracks
type PlaylistTrackRepository interface {
	// AddTrack ajoute un track à une playlist à une position donnée
	AddTrack(ctx context.Context, playlistID, trackID uuid.UUID, position int) error

	// RemoveTrack retire un track d'une playlist
	RemoveTrack(ctx context.Context, playlistID, trackID uuid.UUID) error

	// ReorderTracks réorganise les positions des tracks dans une playlist
	ReorderTracks(ctx context.Context, playlistID uuid.UUID, trackPositions map[uuid.UUID]int) error

	// GetTracks récupère tous les tracks d'une playlist avec leurs informations
	GetTracks(ctx context.Context, playlistID uuid.UUID) ([]*models.PlaylistTrack, error)
}

// playlistTrackRepository implémente PlaylistTrackRepository avec GORM
type playlistTrackRepository struct {
	db *gorm.DB
}

// NewPlaylistTrackRepository crée une nouvelle instance de PlaylistTrackRepository
func NewPlaylistTrackRepository(db *gorm.DB) PlaylistTrackRepository {
	return &playlistTrackRepository{
		db: db,
	}
}

// AddTrack ajoute un track à une playlist à une position donnée
func (r *playlistTrackRepository) AddTrack(ctx context.Context, playlistID, trackID uuid.UUID, position int) error {
	// Vérifier que la playlist existe
	var playlist models.Playlist
	if err := r.db.WithContext(ctx).First(&playlist, "id = ?", playlistID).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errors.New("playlist not found")
		}
		return err
	}

	// Vérifier que le track existe
	var track models.Track
	if err := r.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errors.New("track not found")
		}
		return err
	}

	// Vérifier que le track n'est pas déjà dans la playlist
	var count int64
	if err := r.db.WithContext(ctx).
		Model(&models.PlaylistTrack{}).
		Where("playlist_id = ? AND track_id = ?", playlistID, trackID).
		Count(&count).Error; err != nil {
		// Si erreur due à la structure de la table, on continue
	} else if count > 0 {
		return errors.New("track already in playlist")
	}

	// Si position <= 0, ajouter à la fin
	if position <= 0 {
		var maxPosition int
		r.db.WithContext(ctx).
			Model(&models.PlaylistTrack{}).
			Where("playlist_id = ?", playlistID).
			Select("COALESCE(MAX(position), 0)").
			Scan(&maxPosition)
		position = maxPosition + 1
	} else {
		// Décaler les positions existantes >= position
		if err := r.db.WithContext(ctx).
			Exec("UPDATE playlist_tracks SET position = position + 1 WHERE playlist_id = ? AND position >= ?", playlistID, position).Error; err != nil {
			return err
		}
	}

	// Créer le PlaylistTrack
	playlistTrack := &models.PlaylistTrack{
		PlaylistID: playlistID,
		TrackID:    trackID,
		Position:   position,
	}

	// Utiliser une transaction pour garantir la cohérence
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		// Créer le PlaylistTrack
		if err := tx.Create(playlistTrack).Error; err != nil {
			return err
		}

		// Mettre à jour le TrackCount de la playlist
		if err := tx.Model(&models.Playlist{}).
			Where("id = ?", playlistID).
			Update("track_count", gorm.Expr("track_count + 1")).Error; err != nil {
			return err
		}

		return nil
	})
}

// RemoveTrack retire un track d'une playlist
func (r *playlistTrackRepository) RemoveTrack(ctx context.Context, playlistID, trackID uuid.UUID) error {
	// Vérifier que le PlaylistTrack existe
	var playlistTrack models.PlaylistTrack
	if err := r.db.WithContext(ctx).
		Where("playlist_id = ? AND track_id = ?", playlistID, trackID).
		First(&playlistTrack).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errors.New("track not found in playlist")
		}
		return err
	}

	position := playlistTrack.Position

	// Utiliser une transaction pour garantir la cohérence
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		// Supprimer le PlaylistTrack
		if err := tx.Delete(&playlistTrack).Error; err != nil {
			return err
		}

		// Décaler les positions des tracks suivants
		if position > 0 {
			if err := tx.Exec("UPDATE playlist_tracks SET position = position - 1 WHERE playlist_id = ? AND position > ?", playlistID, position).Error; err != nil {
				return err
			}
		}

		// Mettre à jour le TrackCount de la playlist
		if err := tx.Exec("UPDATE playlists SET track_count = CASE WHEN track_count > 0 THEN track_count - 1 ELSE 0 END WHERE id = ?", playlistID).Error; err != nil {
			return err
		}

		return nil
	})
}

// ReorderTracks réorganise les positions des tracks dans une playlist
func (r *playlistTrackRepository) ReorderTracks(ctx context.Context, playlistID uuid.UUID, trackPositions map[uuid.UUID]int) error {
	if len(trackPositions) == 0 {
		return nil
	}

	// Vérifier que la playlist existe
	var playlist models.Playlist
	if err := r.db.WithContext(ctx).First(&playlist, "id = ?", playlistID).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errors.New("playlist not found")
		}
		return err
	}

	// Utiliser une transaction pour garantir la cohérence
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		// Mettre à jour chaque position
		for trackID, position := range trackPositions {
			if position <= 0 {
				continue // Ignorer les positions invalides
			}

			if err := tx.Model(&models.PlaylistTrack{}).
				Where("playlist_id = ? AND track_id = ?", playlistID, trackID).
				Update("position", position).Error; err != nil {
				return err
			}
		}

		return nil
	})
}

// GetTracks récupère tous les tracks d'une playlist avec leurs informations
func (r *playlistTrackRepository) GetTracks(ctx context.Context, playlistID uuid.UUID) ([]*models.PlaylistTrack, error) {
	var playlistTracks []*models.PlaylistTrack

	// Vérifier si la colonne position existe avant de l'utiliser dans ORDER BY
	query := r.db.WithContext(ctx).
		Where("playlist_id = ?", playlistID).
		Preload("Track").
		Order("position ASC")

	if err := query.Find(&playlistTracks).Error; err != nil {
		return nil, err
	}

	return playlistTracks, nil
}
