package services

import (
	"context"
	"testing"

	"veza-backend-api/internal/models"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

func setupTestPlaylistSearch(t *testing.T) (*PlaylistService, *gorm.DB, *models.User, *models.User, func()) {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Enable foreign keys for SQLite
	db.Exec("PRAGMA foreign_keys = ON")

	// Auto-migrate
	err = db.AutoMigrate(
		&models.User{},
		&models.Playlist{},
	)
	require.NoError(t, err)

	// Create test users
	user1 := &models.User{
		Username:     "user1",
		Email:        "user1@example.com",
		PasswordHash: "hash1",
		Slug:         "user1",
		IsActive:     true,
	}
	user2 := &models.User{
		Username:     "user2",
		Email:        "user2@example.com",
		PasswordHash: "hash2",
		Slug:         "user2",
		IsActive:     true,
	}
	require.NoError(t, db.Create(user1).Error)
	require.NoError(t, db.Create(user2).Error)

	// Create test playlists
	playlist1 := &models.Playlist{
		UserID:        user1.ID,
		Title:         "Rock Playlist",
		Description:   "A rock music playlist",
		IsPublic:      true,
		TrackCount:    0,
		FollowerCount: 0,
	}
	playlist2 := &models.Playlist{
		UserID:        user1.ID,
		Title:         "Private Playlist",
		Description:   "A private playlist",
		IsPublic:      false,
		TrackCount:    0,
		FollowerCount: 0,
	}
	playlist3 := &models.Playlist{
		UserID:        user2.ID,
		Title:         "Jazz Playlist",
		Description:   "A jazz music playlist",
		IsPublic:      true,
		TrackCount:    0,
		FollowerCount: 0,
	}
	playlist4 := &models.Playlist{
		UserID:        user2.ID,
		Title:         "Pop Playlist",
		Description:   "A pop music playlist",
		IsPublic:      true,
		TrackCount:    0,
		FollowerCount: 0,
	}
	require.NoError(t, db.Create(playlist1).Error)
	require.NoError(t, db.Create(playlist2).Error)
	// Force IsPublic to false (GORM might use default value true)
	require.NoError(t, db.Model(playlist2).Update("is_public", false).Error)
	require.NoError(t, db.Create(playlist3).Error)
	require.NoError(t, db.Create(playlist4).Error)

	logger := zap.NewNop()
	service := NewPlaylistServiceWithDB(db, logger)

	cleanup := func() {
		// Database will be closed automatically
	}

	return service, db, user1, user2, cleanup
}

func TestPlaylistService_SearchPlaylists_ByQuery(t *testing.T) {
	service, _, user1, _, cleanup := setupTestPlaylistSearch(t)
	defer cleanup()

	ctx := context.Background()
	userID := &user1.ID

	// Rechercher par "Rock"
	playlists, total, err := service.SearchPlaylists(ctx, SearchPlaylistsParams{
		Query:         "Rock",
		CurrentUserID: userID,
		Page:          1,
		Limit:         20,
	})

	require.NoError(t, err)
	assert.GreaterOrEqual(t, total, int64(1))
	assert.GreaterOrEqual(t, len(playlists), 1)

	// Vérifier que les résultats contiennent "Rock"
	found := false
	for _, p := range playlists {
		if p.Title == "Rock Playlist" {
			found = true
			break
		}
	}
	assert.True(t, found, "Should find Rock Playlist")
}

func TestPlaylistService_SearchPlaylists_ByUserID(t *testing.T) {
	service, _, user1, user2, cleanup := setupTestPlaylistSearch(t)
	defer cleanup()

	ctx := context.Background()
	userID := &user1.ID

	// Rechercher les playlists de user2
	playlists, total, err := service.SearchPlaylists(ctx, SearchPlaylistsParams{
		UserID:        &user2.ID,
		CurrentUserID: userID,
		Page:          1,
		Limit:         20,
	})

	require.NoError(t, err)
	assert.GreaterOrEqual(t, total, int64(2)) // Au moins 2 playlists publiques de user2
	assert.GreaterOrEqual(t, len(playlists), 2)

	// Vérifier que toutes les playlists appartiennent à user2
	for _, p := range playlists {
		assert.Equal(t, user2.ID, p.UserID)
		assert.True(t, p.IsPublic, "Should only return public playlists from other users")
	}
}

func TestPlaylistService_SearchPlaylists_ByIsPublic(t *testing.T) {
	service, _, user1, _, cleanup := setupTestPlaylistSearch(t)
	defer cleanup()

	ctx := context.Background()
	userID := &user1.ID
	public := true

	// Rechercher seulement les playlists publiques
	playlists, total, err := service.SearchPlaylists(ctx, SearchPlaylistsParams{
		IsPublic:      &public,
		CurrentUserID: userID,
		Page:          1,
		Limit:         20,
	})

	require.NoError(t, err)
	assert.GreaterOrEqual(t, total, int64(3)) // Au moins 3 playlists publiques
	assert.GreaterOrEqual(t, len(playlists), 3)

	// Vérifier que toutes les playlists sont publiques
	for _, p := range playlists {
		assert.True(t, p.IsPublic)
	}
}

func TestPlaylistService_SearchPlaylists_OwnPrivatePlaylists(t *testing.T) {
	service, _, user1, _, cleanup := setupTestPlaylistSearch(t)
	defer cleanup()

	ctx := context.Background()
	userID := &user1.ID

	// Rechercher les playlists de user1 (devrait inclure les privées)
	playlists, total, err := service.SearchPlaylists(ctx, SearchPlaylistsParams{
		UserID:        &user1.ID,
		CurrentUserID: userID,
		Page:          1,
		Limit:         20,
	})

	require.NoError(t, err)
	assert.GreaterOrEqual(t, total, int64(2)) // Au moins 2 playlists (1 publique + 1 privée)
	assert.GreaterOrEqual(t, len(playlists), 2)

	// Vérifier qu'on peut voir sa propre playlist privée
	foundPrivate := false
	for _, p := range playlists {
		if p.Title == "Private Playlist" && !p.IsPublic {
			foundPrivate = true
			break
		}
	}
	assert.True(t, foundPrivate, "Should find own private playlist")
}

func TestPlaylistService_SearchPlaylists_Unauthenticated(t *testing.T) {
	service, _, _, _, cleanup := setupTestPlaylistSearch(t)
	defer cleanup()

	ctx := context.Background()

	// Rechercher sans être authentifié (devrait seulement retourner les publiques)
	playlists, total, err := service.SearchPlaylists(ctx, SearchPlaylistsParams{
		Query:         "Playlist",
		CurrentUserID: nil,
		Page:          1,
		Limit:         20,
	})

	require.NoError(t, err)
	assert.GreaterOrEqual(t, total, int64(3)) // Au moins 3 playlists publiques
	assert.GreaterOrEqual(t, len(playlists), 3)

	// Vérifier que toutes les playlists sont publiques
	for _, p := range playlists {
		assert.True(t, p.IsPublic, "Unauthenticated users should only see public playlists")
	}
}

func TestPlaylistService_SearchPlaylists_Pagination(t *testing.T) {
	service, _, user1, _, cleanup := setupTestPlaylistSearch(t)
	defer cleanup()

	ctx := context.Background()
	userID := &user1.ID

	// Première page
	playlists1, total1, err := service.SearchPlaylists(ctx, SearchPlaylistsParams{
		CurrentUserID: userID,
		Page:          1,
		Limit:         2,
	})

	require.NoError(t, err)
	assert.GreaterOrEqual(t, total1, int64(3))
	assert.LessOrEqual(t, len(playlists1), 2)

	// Deuxième page
	playlists2, total2, err := service.SearchPlaylists(ctx, SearchPlaylistsParams{
		CurrentUserID: userID,
		Page:          2,
		Limit:         2,
	})

	require.NoError(t, err)
	assert.Equal(t, total1, total2) // Le total devrait être le même
	assert.LessOrEqual(t, len(playlists2), 2)

	// Vérifier qu'on a des résultats différents
	if len(playlists1) > 0 && len(playlists2) > 0 {
		assert.NotEqual(t, playlists1[0].ID, playlists2[0].ID, "Pages should return different results")
	}
}

func TestPlaylistService_SearchPlaylists_EmptyQuery(t *testing.T) {
	service, _, user1, _, cleanup := setupTestPlaylistSearch(t)
	defer cleanup()

	ctx := context.Background()
	userID := &user1.ID

	// Rechercher sans query (devrait retourner toutes les playlists accessibles)
	playlists, total, err := service.SearchPlaylists(ctx, SearchPlaylistsParams{
		Query:         "",
		CurrentUserID: userID,
		Page:          1,
		Limit:         20,
	})

	require.NoError(t, err)
	assert.GreaterOrEqual(t, total, int64(3)) // Au moins 3 playlists accessibles
	assert.GreaterOrEqual(t, len(playlists), 3)
}
