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/driver/sqlite"
	"gorm.io/gorm"
)

// setupTestDB crée une base de données de test en mémoire (SQLite)
func setupTestDB(t *testing.T) *gorm.DB {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err, "Failed to open test database")

	// Auto-migrate tous les modèles nécessaires
	err = db.AutoMigrate(
		&models.User{},
		&models.Playlist{},
		&models.Track{},
		&models.PlaylistTrack{},
	)
	require.NoError(t, err, "Failed to migrate test database")

	return db
}

// createTestUser crée un utilisateur de test
func createTestUser(t *testing.T, db *gorm.DB) *models.User {
	// Générer un username unique pour éviter les conflits
	timestamp := time.Now().UnixNano()
	username := fmt.Sprintf("testuser_%d", timestamp)
	user := &models.User{
		Username:     username,
		Slug:         username, // Slug doit être unique aussi
		Email:        fmt.Sprintf("%s@example.com", username),
		PasswordHash: "hashed_password",
		IsActive:     true,
		CreatedAt:    time.Now(),
	}
	err := db.Create(user).Error
	require.NoError(t, err)
	return user
}

// createTestPlaylist crée une playlist de test
func createTestPlaylist(t *testing.T, db *gorm.DB, userID uuid.UUID) *models.Playlist {
	playlist := &models.Playlist{
		UserID:      userID,
		Title:       "Test Playlist",
		Description: "Test Description",
		IsPublic:    true,
		TrackCount:  0,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	err := db.Create(playlist).Error
	require.NoError(t, err)
	return playlist
}

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

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

	user := createTestUser(t, db)

	playlist := &models.Playlist{
		UserID:      user.ID,
		Title:       "My Playlist",
		Description: "A test playlist",
		IsPublic:    true,
		TrackCount:  0,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}

	err := repo.Create(ctx, playlist)
	assert.NoError(t, err)
	assert.NotZero(t, playlist.ID)

	// Vérifier que la playlist a été créée
	var found models.Playlist
	err = db.First(&found, playlist.ID).Error
	assert.NoError(t, err)
	assert.Equal(t, "My Playlist", found.Title)
	assert.Equal(t, user.ID, found.UserID)
}

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

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

	// Test GetByID avec playlist existante
	found, err := repo.GetByID(ctx, playlist.ID)
	assert.NoError(t, err)
	assert.NotNil(t, found)
	assert.Equal(t, playlist.ID, found.ID)
	assert.Equal(t, "Test Playlist", found.Title)

	// Test GetByID avec playlist inexistante
	_, err = repo.GetByID(ctx, uuid.New())
	assert.Error(t, err)
	assert.Equal(t, gorm.ErrRecordNotFound, err)
}

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

	user1 := createTestUser(t, db)
	user2 := createTestUser(t, db)

	// Créer 3 playlists pour user1
	_ = createTestPlaylist(t, db, user1.ID)
	playlist2 := createTestPlaylist(t, db, user1.ID)
	playlist2.Title = "Playlist 2"
	db.Save(playlist2)
	playlist3 := createTestPlaylist(t, db, user1.ID)
	playlist3.Title = "Playlist 3"
	db.Save(playlist3)

	// Créer 1 playlist pour user2
	playlist4 := createTestPlaylist(t, db, user2.ID)
	playlist4.Title = "User2 Playlist"
	db.Save(playlist4)

	// Test GetByUserID avec pagination
	playlists, total, err := repo.GetByUserID(ctx, user1.ID, 10, 0)
	assert.NoError(t, err)
	assert.Equal(t, int64(3), total)
	assert.Len(t, playlists, 3)

	// Vérifier que toutes les playlists appartiennent à user1
	for _, p := range playlists {
		assert.Equal(t, user1.ID, p.UserID)
	}

	// Test pagination
	playlists, total, err = repo.GetByUserID(ctx, user1.ID, 2, 0)
	assert.NoError(t, err)
	assert.Equal(t, int64(3), total)
	assert.Len(t, playlists, 2)

	// Test avec offset
	playlists, total, err = repo.GetByUserID(ctx, user1.ID, 2, 2)
	assert.NoError(t, err)
	assert.Equal(t, int64(3), total)
	assert.Len(t, playlists, 1)
}

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

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

	// Mettre à jour la playlist
	playlist.Title = "Updated Title"
	playlist.Description = "Updated Description"
	playlist.IsPublic = false

	err := repo.Update(ctx, playlist)
	assert.NoError(t, err)

	// Vérifier les modifications
	updated, err := repo.GetByID(ctx, playlist.ID)
	assert.NoError(t, err)
	assert.Equal(t, "Updated Title", updated.Title)
	assert.Equal(t, "Updated Description", updated.Description)
	assert.False(t, updated.IsPublic)
}

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

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

	// Supprimer la playlist
	err := repo.Delete(ctx, playlist.ID)
	assert.NoError(t, err)

	// Vérifier que la playlist a été supprimée
	_, err = repo.GetByID(ctx, playlist.ID)
	assert.Error(t, err)
	assert.Equal(t, gorm.ErrRecordNotFound, err)
}

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

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

	// Test Exists avec playlist existante
	exists, err := repo.Exists(ctx, playlist.ID)
	assert.NoError(t, err)
	assert.True(t, exists)

	// Test Exists avec playlist inexistante
	exists, err = repo.Exists(ctx, uuid.New())
	assert.NoError(t, err)
	assert.False(t, exists)
}

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

	user1 := createTestUser(t, db)
	user2 := createTestUser(t, db)

	// Créer des playlists publiques et privées
	public1 := createTestPlaylist(t, db, user1.ID)
	public1.IsPublic = true
	db.Save(public1)

	public2 := createTestPlaylist(t, db, user2.ID)
	public2.IsPublic = true
	db.Save(public2)

	private1 := createTestPlaylist(t, db, user1.ID)
	private1.IsPublic = false
	private1.Title = "Private Playlist"
	db.Save(private1)

	// Test List sans filtres (Anonyme -> Public only implicite via nil viewer)
	playlists, total, err := repo.List(ctx, nil, nil, nil, 10, 0)
	assert.NoError(t, err)
	assert.Equal(t, int64(2), total) // 2 public only
	assert.Len(t, playlists, 2)

	// Test List avec viewerID (Authenticated -> Public + Owned)
	playlists, total, err = repo.List(ctx, nil, &user1.ID, nil, 10, 0)
	assert.NoError(t, err)
	assert.Equal(t, int64(3), total) // 2 public + 1 private owned
	assert.Len(t, playlists, 3)

	// Test List avec filtre userID (Regarder un autre user)
	// Pas de viewerID passé, donc on assume anonyme ou on passe isPublic=true explicitement
	// Le service gère la logique isPublic=true si viewer != filterUser
	isPublic := true
	playlists, total, err = repo.List(ctx, &user1.ID, nil, &isPublic, 10, 0)
	assert.NoError(t, err)
	assert.Equal(t, int64(1), total) // 1 public owned by user1
	assert.Len(t, playlists, 1)
	assert.Equal(t, user1.ID, playlists[0].UserID)
	assert.True(t, playlists[0].IsPublic)

	// Test List avec filtre isPublic explicit
	playlists, total, err = repo.List(ctx, nil, nil, &isPublic, 10, 0)
	assert.NoError(t, err)
	assert.Equal(t, int64(2), total)
	assert.Len(t, playlists, 2)
	for _, p := range playlists {
		assert.True(t, p.IsPublic)
	}

	// Test List avec filtres combinés (User + Private + Viewer=Owner)
	isPrivate := false
	playlists, total, err = repo.List(ctx, &user1.ID, &user1.ID, &isPrivate, 10, 0)
	assert.NoError(t, err)
	assert.Equal(t, int64(1), total)
	assert.Len(t, playlists, 1)
	assert.Equal(t, user1.ID, playlists[0].UserID)
	assert.False(t, playlists[0].IsPublic)

	// Test pagination
	playlists, total, err = repo.List(ctx, nil, &user1.ID, nil, 2, 0)
	assert.NoError(t, err)
	assert.Equal(t, int64(3), total)
	assert.Len(t, playlists, 2)
}

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

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

	// Créer un track de test
	track := &models.Track{
		UserID:    user.ID,
		Title:     "Test Track",
		Artist:    "Test Artist",
		Duration:  180,
		FilePath:  "/path/to/track.mp3",
		FileSize:  1024,
		Format:    "mp3",
		IsPublic:  true,
		Status:    "ready",
		CreatedAt: time.Now(),
	}
	err := db.Create(track).Error
	require.NoError(t, err)

	// playlist_tracks table IS available in Postgres (migrations run)

	// Essayer d'ajouter le track à la playlist
	playlistTrack := &models.PlaylistTrack{
		PlaylistID: playlist.ID,
		TrackID:    track.ID,
	}

	err = db.Create(playlistTrack).Error
	require.NoError(t, err, "Failed to create playlist_track")

	// Récupérer la playlist avec ses tracks
	found, err := repo.GetByID(ctx, playlist.ID)
	assert.NoError(t, err)
	assert.NotNil(t, found)
	if len(found.Tracks) > 0 {
		assert.Equal(t, track.ID, found.Tracks[0].TrackID)
		// Vérifier que le track est chargé (Track est une valeur, pas un pointeur)
		if found.Tracks[0].Track.ID != uuid.Nil {
			assert.Equal(t, "Test Track", found.Tracks[0].Track.Title)
		}
	}
}
