package repositories

import (
	"context"
	"fmt"
	"testing"
	"time"

	"veza-backend-api/internal/models"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/gorm"
)

// createTestTrack crée un track de test
func createTestTrack(t *testing.T, db *gorm.DB, userID uuid.UUID) *models.Track {
	track := &models.Track{
		UserID:    userID,
		Title:     fmt.Sprintf("Test Track %d", time.Now().UnixNano()),
		Artist:    "Test Artist",
		Duration:  180,
		FilePath:  "/path/to/track.mp3",
		FileSize:  1024 * 1024,
		Format:    "mp3",
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	err := db.Create(track).Error
	require.NoError(t, err)
	return track
}

func TestNewPlaylistTrackRepository(t *testing.T) {
	db := setupTestDB(t)
	repo := NewPlaylistTrackRepository(db)
	assert.NotNil(t, repo)
}

func TestPlaylistTrackRepository_AddTrack(t *testing.T) {
	db := setupTestDB(t)
	repo := NewPlaylistTrackRepository(db)
	ctx := context.Background()

	user := createTestUser(t, db)
	playlist := createTestPlaylist(t, db, user.ID)
	track := createTestTrack(t, db, user.ID)

	// Test AddTrack avec position spécifiée
	err := repo.AddTrack(ctx, playlist.ID, track.ID, 1)
	assert.NoError(t, err)

	// Vérifier que le PlaylistTrack a été créé
	var playlistTrack models.PlaylistTrack
	err = db.Where("playlist_id = ? AND track_id = ?", playlist.ID, track.ID).First(&playlistTrack).Error
	assert.NoError(t, err)
	assert.Equal(t, 1, playlistTrack.Position)
	assert.Equal(t, playlist.ID, playlistTrack.PlaylistID)
	assert.Equal(t, track.ID, playlistTrack.TrackID)

	// Vérifier que le TrackCount a été mis à jour
	var updatedPlaylist models.Playlist
	err = db.First(&updatedPlaylist, playlist.ID).Error
	assert.NoError(t, err)
	assert.Equal(t, 1, updatedPlaylist.TrackCount)

	// Test AddTrack avec position 0 (ajout à la fin)
	track2 := createTestTrack(t, db, user.ID)
	err = repo.AddTrack(ctx, playlist.ID, track2.ID, 0)
	assert.NoError(t, err)

	var playlistTrack2 models.PlaylistTrack
	err = db.Where("playlist_id = ? AND track_id = ?", playlist.ID, track2.ID).First(&playlistTrack2).Error
	assert.NoError(t, err)
	assert.Equal(t, 2, playlistTrack2.Position)

	// Test AddTrack avec track déjà présent
	err = repo.AddTrack(ctx, playlist.ID, track.ID, 1)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "already in playlist")

	// Test AddTrack avec playlist inexistante
	err = repo.AddTrack(ctx, uuid.New(), track.ID, 1)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "playlist not found")

	// Test AddTrack avec track inexistant
	err = repo.AddTrack(ctx, playlist.ID, uuid.New(), 1)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "track not found")
}

func TestPlaylistTrackRepository_AddTrack_WithPositionShift(t *testing.T) {
	db := setupTestDB(t)
	repo := NewPlaylistTrackRepository(db)
	ctx := context.Background()

	user := createTestUser(t, db)
	playlist := createTestPlaylist(t, db, user.ID)

	// Ajouter 3 tracks
	track1 := createTestTrack(t, db, user.ID)
	track2 := createTestTrack(t, db, user.ID)
	track3 := createTestTrack(t, db, user.ID)

	err := repo.AddTrack(ctx, playlist.ID, track1.ID, 1)
	assert.NoError(t, err)
	err = repo.AddTrack(ctx, playlist.ID, track2.ID, 2)
	assert.NoError(t, err)
	err = repo.AddTrack(ctx, playlist.ID, track3.ID, 3)
	assert.NoError(t, err)

	// Ajouter un track au milieu (position 2)
	track4 := createTestTrack(t, db, user.ID)
	err = repo.AddTrack(ctx, playlist.ID, track4.ID, 2)
	assert.NoError(t, err)

	// Vérifier les positions
	tracks, err := repo.GetTracks(ctx, playlist.ID)
	assert.NoError(t, err)
	assert.Len(t, tracks, 4)

	// Vérifier que les positions sont correctes
	positions := make(map[uuid.UUID]int)
	for _, pt := range tracks {
		positions[pt.TrackID] = pt.Position
	}

	assert.Equal(t, 1, positions[track1.ID])
	assert.Equal(t, 2, positions[track4.ID]) // Nouveau track à la position 2
	assert.Equal(t, 3, positions[track2.ID]) // Décalé de 2 à 3
	assert.Equal(t, 4, positions[track3.ID]) // Décalé de 3 à 4
}

func TestPlaylistTrackRepository_RemoveTrack(t *testing.T) {
	db := setupTestDB(t)
	repo := NewPlaylistTrackRepository(db)
	ctx := context.Background()

	user := createTestUser(t, db)
	playlist := createTestPlaylist(t, db, user.ID)
	track1 := createTestTrack(t, db, user.ID)
	track2 := createTestTrack(t, db, user.ID)
	track3 := createTestTrack(t, db, user.ID)

	// Ajouter 3 tracks
	err := repo.AddTrack(ctx, playlist.ID, track1.ID, 1)
	assert.NoError(t, err)
	err = repo.AddTrack(ctx, playlist.ID, track2.ID, 2)
	assert.NoError(t, err)
	err = repo.AddTrack(ctx, playlist.ID, track3.ID, 3)
	assert.NoError(t, err)

	// Retirer le track du milieu
	err = repo.RemoveTrack(ctx, playlist.ID, track2.ID)
	assert.NoError(t, err)

	// Vérifier que le track a été retiré
	var count int64
	db.Model(&models.PlaylistTrack{}).
		Where("playlist_id = ? AND track_id = ?", playlist.ID, track2.ID).
		Count(&count)
	assert.Equal(t, int64(0), count)

	// Vérifier que les positions ont été décalées
	tracks, err := repo.GetTracks(ctx, playlist.ID)
	assert.NoError(t, err)
	assert.Len(t, tracks, 2)

	positions := make(map[uuid.UUID]int)
	for _, pt := range tracks {
		positions[pt.TrackID] = pt.Position
	}

	assert.Equal(t, 1, positions[track1.ID])
	assert.Equal(t, 2, positions[track3.ID]) // Décalé de 3 à 2

	// Vérifier que le TrackCount a été mis à jour
	var updatedPlaylist models.Playlist
	err = db.First(&updatedPlaylist, playlist.ID).Error
	assert.NoError(t, err)
	assert.Equal(t, 2, updatedPlaylist.TrackCount)

	// Test RemoveTrack avec track non présent
	err = repo.RemoveTrack(ctx, playlist.ID, uuid.New())
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "not found in playlist")
}

func TestPlaylistTrackRepository_ReorderTracks(t *testing.T) {
	db := setupTestDB(t)
	repo := NewPlaylistTrackRepository(db)
	ctx := context.Background()

	user := createTestUser(t, db)
	playlist := createTestPlaylist(t, db, user.ID)

	// Ajouter 3 tracks
	track1 := createTestTrack(t, db, user.ID)
	track2 := createTestTrack(t, db, user.ID)
	track3 := createTestTrack(t, db, user.ID)

	err := repo.AddTrack(ctx, playlist.ID, track1.ID, 1)
	assert.NoError(t, err)
	err = repo.AddTrack(ctx, playlist.ID, track2.ID, 2)
	assert.NoError(t, err)
	err = repo.AddTrack(ctx, playlist.ID, track3.ID, 3)
	assert.NoError(t, err)

	// Réorganiser: track3 -> position 1, track1 -> position 2, track2 -> position 3
	trackPositions := map[uuid.UUID]int{
		track3.ID: 1,
		track1.ID: 2,
		track2.ID: 3,
	}

	err = repo.ReorderTracks(ctx, playlist.ID, trackPositions)
	assert.NoError(t, err)

	// Vérifier les nouvelles positions
	tracks, err := repo.GetTracks(ctx, playlist.ID)
	assert.NoError(t, err)
	assert.Len(t, tracks, 3)

	positions := make(map[uuid.UUID]int)
	for _, pt := range tracks {
		positions[pt.TrackID] = pt.Position
	}

	assert.Equal(t, 1, positions[track3.ID])
	assert.Equal(t, 2, positions[track1.ID])
	assert.Equal(t, 3, positions[track2.ID])

	// Test ReorderTracks avec playlist inexistante
	err = repo.ReorderTracks(ctx, uuid.New(), trackPositions)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "playlist not found")
}

func TestPlaylistTrackRepository_GetTracks(t *testing.T) {
	db := setupTestDB(t)
	repo := NewPlaylistTrackRepository(db)
	ctx := context.Background()

	user := createTestUser(t, db)
	playlist := createTestPlaylist(t, db, user.ID)

	// Ajouter 3 tracks
	track1 := createTestTrack(t, db, user.ID)
	track2 := createTestTrack(t, db, user.ID)
	track3 := createTestTrack(t, db, user.ID)

	err := repo.AddTrack(ctx, playlist.ID, track1.ID, 1)
	assert.NoError(t, err)
	err = repo.AddTrack(ctx, playlist.ID, track2.ID, 2)
	assert.NoError(t, err)
	err = repo.AddTrack(ctx, playlist.ID, track3.ID, 3)
	assert.NoError(t, err)

	// Récupérer les tracks
	tracks, err := repo.GetTracks(ctx, playlist.ID)
	assert.NoError(t, err)
	assert.Len(t, tracks, 3)

	// Vérifier que les tracks sont présents
	trackIDs := make(map[uuid.UUID]bool)
	trackTitles := make(map[uuid.UUID]string)
	for _, pt := range tracks {
		trackIDs[pt.TrackID] = true
		trackTitles[pt.TrackID] = pt.Track.Title
		assert.NotZero(t, pt.Track.ID)
		assert.NotEmpty(t, pt.Track.Title)
	}

	// Vérifier que tous les tracks sont présents
	assert.True(t, trackIDs[track1.ID], "track1 should be in playlist")
	assert.True(t, trackIDs[track2.ID], "track2 should be in playlist")
	assert.True(t, trackIDs[track3.ID], "track3 should be in playlist")

	// Vérifier que les informations du track sont chargées
	assert.Equal(t, track1.Title, trackTitles[track1.ID])
	assert.Equal(t, track2.Title, trackTitles[track2.ID])
	assert.Equal(t, track3.Title, trackTitles[track3.ID])

	// Test GetTracks avec playlist vide
	playlist2 := createTestPlaylist(t, db, user.ID)
	tracks, err = repo.GetTracks(ctx, playlist2.ID)
	assert.NoError(t, err)
	assert.Len(t, tracks, 0)
}
