package services

import (
	"context"
	"os"
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/redis/go-redis/v9"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
)

// setupTestCacheService crée un CacheService de test avec Redis
func setupTestCacheService(t *testing.T) (*CacheService, *redis.Client) {
	redisURL := os.Getenv("REDIS_TEST_URL")
	if redisURL == "" {
		redisURL = "redis://localhost:6379/15" // Utilise DB 15 pour les tests
	}

	opts, err := redis.ParseURL(redisURL)
	if err != nil {
		t.Skipf("Skipping test: failed to parse Redis URL: %v", err)
		return nil, nil
	}

	client := redis.NewClient(opts)

	// Test de connexion
	ctx := context.Background()
	_, err = client.Ping(ctx).Result()
	if err != nil {
		t.Skipf("Skipping test: Redis not available: %v", err)
		return nil, nil
	}

	// Nettoyer la base de données de test
	client.FlushDB(ctx)

	// Cleanup: Flush DB après les tests
	t.Cleanup(func() {
		client.FlushDB(ctx)
		client.Close()
	})

	logger := zap.NewNop()
	service := NewCacheService(client, logger)

	return service, client
}

func TestCacheService_SetAndGet_Success(t *testing.T) {
	service, _ := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	key := "test:key"
	value := map[string]interface{}{
		"name":  "test",
		"value": 123,
	}
	ttl := 5 * time.Minute

	// Set
	err := service.Set(ctx, key, value, ttl)
	assert.NoError(t, err)

	// Get
	var retrieved map[string]interface{}
	err = service.Get(ctx, key, &retrieved)
	assert.NoError(t, err)
	assert.Equal(t, value["name"], retrieved["name"])
	assert.Equal(t, float64(123), retrieved["value"]) // JSON unmarshals numbers as float64
}

func TestCacheService_Get_CacheMiss(t *testing.T) {
	service, _ := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	key := "test:nonexistent"

	var value map[string]interface{}
	err := service.Get(ctx, key, &value)
	assert.Error(t, err)
	assert.Equal(t, ErrCacheMiss, err)
}

func TestCacheService_Delete_Success(t *testing.T) {
	service, _ := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	key := "test:delete"
	value := "test value"
	ttl := 5 * time.Minute

	// Set
	err := service.Set(ctx, key, value, ttl)
	assert.NoError(t, err)

	// Verify exists
	var retrieved string
	err = service.Get(ctx, key, &retrieved)
	assert.NoError(t, err)

	// Delete
	err = service.Delete(ctx, key)
	assert.NoError(t, err)

	// Verify deleted
	err = service.Get(ctx, key, &retrieved)
	assert.Error(t, err)
	assert.Equal(t, ErrCacheMiss, err)
}

func TestCacheService_Exists_True(t *testing.T) {
	service, _ := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	key := "test:exists"
	value := "test value"
	ttl := 5 * time.Minute

	// Set
	err := service.Set(ctx, key, value, ttl)
	assert.NoError(t, err)

	// Check exists
	exists, err := service.Exists(ctx, key)
	assert.NoError(t, err)
	assert.True(t, exists)
}

func TestCacheService_Exists_False(t *testing.T) {
	service, _ := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	key := "test:nonexistent"

	exists, err := service.Exists(ctx, key)
	assert.NoError(t, err)
	assert.False(t, exists)
}

func TestCacheService_SetUser_Success(t *testing.T) {
	service, _ := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	userID := uuid.New()
	user := map[string]interface{}{
		"id":       userID.String(),
		"username": "testuser",
		"email":    "test@example.com",
	}
	config := DefaultCacheConfig()

	err := service.SetUser(ctx, userID, user, config)
	assert.NoError(t, err)

	// Verify
	var retrieved map[string]interface{}
	err = service.GetUser(ctx, userID, &retrieved)
	assert.NoError(t, err)
	assert.Equal(t, user["username"], retrieved["username"])
}

func TestCacheService_DeleteUser_Success(t *testing.T) {
	service, _ := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	userID := uuid.New()
	user := map[string]interface{}{
		"id":       userID.String(),
		"username": "testuser",
	}
	config := DefaultCacheConfig()

	// Set
	err := service.SetUser(ctx, userID, user, config)
	assert.NoError(t, err)

	// Delete
	err = service.DeleteUser(ctx, userID)
	assert.NoError(t, err)

	// Verify deleted
	var retrieved map[string]interface{}
	err = service.GetUser(ctx, userID, &retrieved)
	assert.Error(t, err)
	assert.Equal(t, ErrCacheMiss, err)
}

func TestCacheService_SetTrack_Success(t *testing.T) {
	service, _ := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	trackID := uuid.New()
	track := map[string]interface{}{
		"id":       trackID.String(),
		"title":    "Test Track",
		"duration": 180,
	}
	config := DefaultCacheConfig()

	err := service.SetTrack(ctx, trackID, track, config)
	assert.NoError(t, err)

	// Verify
	var retrieved map[string]interface{}
	err = service.GetTrack(ctx, trackID, &retrieved)
	assert.NoError(t, err)
	assert.Equal(t, track["title"], retrieved["title"])
}

func TestCacheService_DeleteTrack_Success(t *testing.T) {
	service, _ := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	trackID := uuid.New()
	track := map[string]interface{}{
		"id":    trackID.String(),
		"title": "Test Track",
	}
	config := DefaultCacheConfig()

	// Set
	err := service.SetTrack(ctx, trackID, track, config)
	assert.NoError(t, err)

	// Delete
	err = service.DeleteTrack(ctx, trackID)
	assert.NoError(t, err)

	// Verify deleted
	var retrieved map[string]interface{}
	err = service.GetTrack(ctx, trackID, &retrieved)
	assert.Error(t, err)
	assert.Equal(t, ErrCacheMiss, err)
}

func TestCacheService_SetPlaylist_Success(t *testing.T) {
	service, _ := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	playlistID := uuid.New()
	playlist := map[string]interface{}{
		"id":     playlistID.String(),
		"name":   "Test Playlist",
		"tracks": []string{},
	}
	config := DefaultCacheConfig()

	err := service.SetPlaylist(ctx, playlistID, playlist, config)
	assert.NoError(t, err)

	// Verify
	var retrieved map[string]interface{}
	err = service.GetPlaylist(ctx, playlistID, &retrieved)
	assert.NoError(t, err)
	assert.Equal(t, playlist["name"], retrieved["name"])
}

func TestCacheService_DeletePlaylist_Success(t *testing.T) {
	service, _ := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	playlistID := uuid.New()
	playlist := map[string]interface{}{
		"id":   playlistID.String(),
		"name": "Test Playlist",
	}
	config := DefaultCacheConfig()

	// Set
	err := service.SetPlaylist(ctx, playlistID, playlist, config)
	assert.NoError(t, err)

	// Delete
	err = service.DeletePlaylist(ctx, playlistID)
	assert.NoError(t, err)

	// Verify deleted
	var retrieved map[string]interface{}
	err = service.GetPlaylist(ctx, playlistID, &retrieved)
	assert.Error(t, err)
	assert.Equal(t, ErrCacheMiss, err)
}

func TestCacheService_DeletePattern_Success(t *testing.T) {
	service, client := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	pattern := "test:pattern:*"

	// Set multiple keys matching pattern
	keys := []string{"test:pattern:1", "test:pattern:2", "test:pattern:3"}
	for _, key := range keys {
		err := client.Set(ctx, key, "value", 5*time.Minute).Err()
		require.NoError(t, err)
	}

	// Set a key that doesn't match
	err := client.Set(ctx, "test:other:1", "value", 5*time.Minute).Err()
	require.NoError(t, err)

	// Delete pattern
	err = service.DeletePattern(ctx, pattern)
	assert.NoError(t, err)

	// Verify pattern keys deleted
	for _, key := range keys {
		exists, err := client.Exists(ctx, key).Result()
		assert.NoError(t, err)
		assert.Equal(t, int64(0), exists)
	}

	// Verify non-matching key still exists
	exists, err := client.Exists(ctx, "test:other:1").Result()
	assert.NoError(t, err)
	assert.Equal(t, int64(1), exists)
}

func TestCacheService_SetUserTracks_Success(t *testing.T) {
	service, _ := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	userID := uuid.New()
	page := 1
	tracks := []map[string]interface{}{
		{"id": uuid.New().String(), "title": "Track 1"},
		{"id": uuid.New().String(), "title": "Track 2"},
	}
	config := DefaultCacheConfig()

	err := service.SetUserTracks(ctx, userID, page, tracks, config)
	assert.NoError(t, err)

	// Verify
	var retrieved []map[string]interface{}
	err = service.GetUserTracks(ctx, userID, page, &retrieved)
	assert.NoError(t, err)
	assert.Len(t, retrieved, 2)
}

func TestCacheService_DeleteUserTracks_Success(t *testing.T) {
	service, _ := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	userID := uuid.New()
	page := 1
	tracks := []map[string]interface{}{
		{"id": uuid.New().String(), "title": "Track 1"},
	}
	config := DefaultCacheConfig()

	// Set
	err := service.SetUserTracks(ctx, userID, page, tracks, config)
	assert.NoError(t, err)

	// Delete
	err = service.DeleteUserTracks(ctx, userID)
	assert.NoError(t, err)

	// Verify deleted
	var retrieved []map[string]interface{}
	err = service.GetUserTracks(ctx, userID, page, &retrieved)
	assert.Error(t, err)
	assert.Equal(t, ErrCacheMiss, err)
}

func TestCacheService_SetSearchResults_Success(t *testing.T) {
	service, _ := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	query := "test query"
	results := map[string]interface{}{
		"tracks": []string{"track1", "track2"},
		"users":  []string{"user1"},
	}
	config := DefaultCacheConfig()

	err := service.SetSearchResults(ctx, query, results, config)
	assert.NoError(t, err)

	// Verify
	var retrieved map[string]interface{}
	err = service.GetSearchResults(ctx, query, &retrieved)
	assert.NoError(t, err)
	assert.NotNil(t, retrieved["tracks"])
}

func TestCacheService_InvalidateUserCache_Success(t *testing.T) {
	service, client := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	userID := uuid.New()
	config := DefaultCacheConfig()

	// Set various user-related cache entries
	err := service.SetUser(ctx, userID, map[string]interface{}{"id": userID.String()}, config)
	assert.NoError(t, err)

	err = service.SetUserTracks(ctx, userID, 1, []map[string]interface{}{}, config)
	assert.NoError(t, err)

	// Set a user session key manually
	err = client.Set(ctx, "user_sessions:"+userID.String()+":session1", "value", 5*time.Minute).Err()
	require.NoError(t, err)

	// Invalidate
	err = service.InvalidateUserCache(ctx, userID)
	assert.NoError(t, err)

	// Verify user cache deleted
	var retrieved map[string]interface{}
	err = service.GetUser(ctx, userID, &retrieved)
	assert.Error(t, err)
	assert.Equal(t, ErrCacheMiss, err)
}

func TestCacheService_InvalidateTrackCache_Success(t *testing.T) {
	service, client := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	trackID := uuid.New()
	config := DefaultCacheConfig()

	// Set track cache
	err := service.SetTrack(ctx, trackID, map[string]interface{}{"id": trackID.String()}, config)
	assert.NoError(t, err)

	// Set search results (should be invalidated)
	err = client.Set(ctx, "search:test", "results", 5*time.Minute).Err()
	require.NoError(t, err)

	// Invalidate
	err = service.InvalidateTrackCache(ctx, trackID)
	assert.NoError(t, err)

	// Verify track cache deleted
	var retrieved map[string]interface{}
	err = service.GetTrack(ctx, trackID, &retrieved)
	assert.Error(t, err)
	assert.Equal(t, ErrCacheMiss, err)
}

func TestCacheService_InvalidatePlaylistCache_Success(t *testing.T) {
	service, client := setupTestCacheService(t)
	if service == nil {
		return
	}

	ctx := context.Background()
	playlistID := uuid.New()
	config := DefaultCacheConfig()

	// Set playlist cache
	err := service.SetPlaylist(ctx, playlistID, map[string]interface{}{"id": playlistID.String()}, config)
	assert.NoError(t, err)

	// Set search results (should be invalidated)
	err = client.Set(ctx, "search:test", "results", 5*time.Minute).Err()
	require.NoError(t, err)

	// Invalidate
	err = service.InvalidatePlaylistCache(ctx, playlistID)
	assert.NoError(t, err)

	// Verify playlist cache deleted
	var retrieved map[string]interface{}
	err = service.GetPlaylist(ctx, playlistID, &retrieved)
	assert.Error(t, err)
	assert.Equal(t, ErrCacheMiss, err)
}

func TestCacheService_DefaultCacheConfig(t *testing.T) {
	config := DefaultCacheConfig()
	assert.NotNil(t, config)
	assert.Equal(t, 5*time.Minute, config.DefaultTTL)
	assert.Equal(t, 5*time.Minute, config.UserTTL)
	assert.Equal(t, 30*time.Minute, config.TrackTTL)
	assert.Equal(t, 15*time.Minute, config.PlaylistTTL)
	assert.Equal(t, 1*time.Minute, config.RoomTTL)
}
