package services

import (
	"context"
	"github.com/google/uuid"
	"testing"
	"time"

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

	"veza-backend-api/internal/models"
)

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

	db.Exec("PRAGMA foreign_keys = ON")

	err = db.AutoMigrate(&models.User{}, &models.Track{}, &models.PlaybackAnalytics{})
	require.NoError(t, err)

	logger := zaptest.NewLogger(t)
	service := NewPlaybackHeatmapService(db, logger)

	return db, service
}

func TestNewPlaybackHeatmapService(t *testing.T) {
	db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	logger := zaptest.NewLogger(t)

	service := NewPlaybackHeatmapService(db, logger)

	assert.NotNil(t, service)
	assert.Equal(t, db, service.db)
	assert.NotNil(t, service.logger)
}

func TestNewPlaybackHeatmapService_NilLogger(t *testing.T) {
	db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})

	service := NewPlaybackHeatmapService(db, nil)

	assert.NotNil(t, service)
	assert.NotNil(t, service.logger)
}

func TestPlaybackHeatmapService_GenerateHeatmap_NoSessions(t *testing.T) {
	db, service := setupTestPlaybackHeatmapServiceDB(t)
	ctx := context.Background()

	// Créer user et track
	userID := uuid.New()
	trackID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180, // 3 minutes
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	result, err := service.GenerateHeatmap(ctx, trackID, 5)

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Equal(t, trackID, result.TrackID)
	assert.Equal(t, 180, result.TrackDuration)
	assert.Equal(t, 5, result.SegmentSize)
	assert.Equal(t, int64(0), result.TotalSessions)
	assert.NotNil(t, result.Segments)
}

func TestPlaybackHeatmapService_GenerateHeatmap_InvalidTrackID(t *testing.T) {
	_, service := setupTestPlaybackHeatmapServiceDB(t)
	ctx := context.Background()

	result, err := service.GenerateHeatmap(ctx, uuid.Nil, 5)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid track ID")
	assert.Nil(t, result)
}

func TestPlaybackHeatmapService_GenerateHeatmap_TrackNotFound(t *testing.T) {
	_, service := setupTestPlaybackHeatmapServiceDB(t)
	ctx := context.Background()

	result, err := service.GenerateHeatmap(ctx, uuid.New(), 5)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "track not found")
	assert.Nil(t, result)
}

func TestPlaybackHeatmapService_GenerateHeatmap_WithSessions(t *testing.T) {
	db, service := setupTestPlaybackHeatmapServiceDB(t)
	ctx := context.Background()

	// Créer user et track
	userID := uuid.New()
	trackID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180, // 3 minutes
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Créer des analytics avec différents temps de lecture
	now := time.Now()
	analytics1 := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       90, // 50% de 180
		PauseCount:     2,
		SeekCount:      1,
		CompletionRate: 50.0,
		StartedAt:      now,
		CreatedAt:      now,
	}
	analytics2 := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       180, // 100% de 180
		PauseCount:     0,
		SeekCount:      0,
		CompletionRate: 100.0,
		StartedAt:      now,
		CreatedAt:      now,
	}
	db.Create(analytics1)
	db.Create(analytics2)

	result, err := service.GenerateHeatmap(ctx, trackID, 10) // Segments de 10 secondes

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Equal(t, trackID, result.TrackID)
	assert.Equal(t, 180, result.TrackDuration)
	assert.Equal(t, 10, result.SegmentSize)
	assert.Equal(t, int64(2), result.TotalSessions)
	assert.Greater(t, len(result.Segments), 0)

	// Vérifier que les premiers segments ont été écoutés
	if len(result.Segments) > 0 {
		assert.Greater(t, result.Segments[0].ListenCount, int64(0))
	}
}

func TestPlaybackHeatmapService_GenerateHeatmap_DefaultSegmentSize(t *testing.T) {
	db, service := setupTestPlaybackHeatmapServiceDB(t)
	ctx := context.Background()

	// Créer user et track
	userID := uuid.New()
	trackID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Utiliser 0 pour le segmentSize (devrait utiliser la valeur par défaut de 5)
	result, err := service.GenerateHeatmap(ctx, trackID, 0)

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Equal(t, 5, result.SegmentSize) // Valeur par défaut
}

func TestPlaybackHeatmapService_GenerateHeatmap_MaxSegmentSize(t *testing.T) {
	db, service := setupTestPlaybackHeatmapServiceDB(t)
	ctx := context.Background()

	// Créer user et track
	userID := uuid.New()
	trackID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Utiliser un nombre très élevé (devrait être limité à 60)
	result, err := service.GenerateHeatmap(ctx, trackID, 200)

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Equal(t, 60, result.SegmentSize) // Maximum
}

func TestPlaybackHeatmapService_GenerateHeatmap_InvalidDuration(t *testing.T) {
	db, service := setupTestPlaybackHeatmapServiceDB(t)
	ctx := context.Background()

	// Créer user et track avec durée invalide
	userID := uuid.New()
	trackID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 0, // Durée invalide
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	result, err := service.GenerateHeatmap(ctx, trackID, 5)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid duration")
	assert.Nil(t, result)
}

func TestPlaybackHeatmapService_CalculateListenedZones(t *testing.T) {
	_, service := setupTestPlaybackHeatmapServiceDB(t)

	// Créer des analytics avec différents temps de lecture
	analytics := []models.PlaybackAnalytics{
		{PlayTime: 30, CompletionRate: 16.67},  // 30 secondes sur 180
		{PlayTime: 60, CompletionRate: 33.33},  // 60 secondes sur 180
		{PlayTime: 180, CompletionRate: 100.0}, // 180 secondes (complet)
	}

	zones := service.calculateListenedZones(analytics, 180, 10) // Segments de 10 secondes

	assert.NotNil(t, zones)
	assert.Greater(t, len(zones), 0)

	// Vérifier que les premiers segments ont été écoutés
	if zones[0] != nil {
		assert.Greater(t, zones[0].ListenCount, int64(0))
	}

	// Vérifier que le segment 0-10 a été écouté par toutes les sessions
	if zones[0] != nil {
		assert.Equal(t, int64(3), zones[0].ListenCount) // 3 sessions ont atteint le premier segment
	}
}

func TestPlaybackHeatmapService_CalculateSkipZones(t *testing.T) {
	_, service := setupTestPlaybackHeatmapServiceDB(t)

	// Créer des analytics avec des seeks (indiquant des skips)
	analytics := []models.PlaybackAnalytics{
		{PlayTime: 30, SeekCount: 2, CompletionRate: 16.67},  // 2 seeks, lecture courte
		{PlayTime: 60, SeekCount: 1, CompletionRate: 33.33},  // 1 seek
		{PlayTime: 180, SeekCount: 0, CompletionRate: 100.0}, // Pas de seeks
	}

	zones := service.calculateSkipZones(analytics, 180, 10)

	assert.NotNil(t, zones)
	assert.Greater(t, len(zones), 0)
}

func TestPlaybackHeatmapService_GenerateHeatmapSegments(t *testing.T) {
	_, service := setupTestPlaybackHeatmapServiceDB(t)

	// Créer des zones écoutées et skip
	listenedZones := make(map[int]*ListenedZone)
	listenedZones[0] = &ListenedZone{
		StartTime:     0.0,
		EndTime:       10.0,
		ListenCount:   3,
		TotalPlayTime: 30.0,
		SessionCount:  3,
	}
	listenedZones[1] = &ListenedZone{
		StartTime:     10.0,
		EndTime:       20.0,
		ListenCount:   2,
		TotalPlayTime: 20.0,
		SessionCount:  2,
	}

	skipZones := make(map[int]*SkipZone)
	skipZones[0] = &SkipZone{
		StartTime: 0.0,
		EndTime:   10.0,
		SkipCount: 0,
	}
	skipZones[1] = &SkipZone{
		StartTime: 10.0,
		EndTime:   20.0,
		SkipCount: 1,
	}

	segments := service.generateHeatmapSegments(listenedZones, skipZones, 180, 10)

	assert.NotNil(t, segments)
	assert.Greater(t, len(segments), 0)

	// Vérifier que les segments ont des intensités calculées
	if len(segments) > 0 {
		assert.GreaterOrEqual(t, segments[0].Intensity, 0.0)
		assert.Greater(t, segments[0].ListenCount, int64(0))
	}
}

func TestPlaybackHeatmapService_GetHeatmapIntensityArray(t *testing.T) {
	db, service := setupTestPlaybackHeatmapServiceDB(t)
	ctx := context.Background()

	// Créer user et track
	userID := uuid.New()
	trackID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Créer des analytics
	now := time.Now()
	analytics := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       90,
		PauseCount:     1,
		SeekCount:      0,
		CompletionRate: 50.0,
		StartedAt:      now,
		CreatedAt:      now,
	}
	db.Create(analytics)

	intensities, err := service.GetHeatmapIntensityArray(ctx, trackID, 10)

	require.NoError(t, err)
	assert.NotNil(t, intensities)
	assert.Greater(t, len(intensities), 0)

	// Vérifier que les intensités sont normalisées (0-1)
	for _, intensity := range intensities {
		assert.GreaterOrEqual(t, intensity, 0.0)
		assert.LessOrEqual(t, intensity, 1.0)
	}
}

func TestPlaybackHeatmapService_GenerateHeatmap_WithSkips(t *testing.T) {
	db, service := setupTestPlaybackHeatmapServiceDB(t)
	ctx := context.Background()

	// Créer user et track
	userID := uuid.New()
	trackID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Créer des analytics avec des seeks (skips)
	now := time.Now()
	analytics := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       60,
		PauseCount:     0,
		SeekCount:      3, // 3 seeks = skips
		CompletionRate: 33.33,
		StartedAt:      now,
		CreatedAt:      now,
	}
	db.Create(analytics)

	result, err := service.GenerateHeatmap(ctx, trackID, 10)

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Greater(t, len(result.Segments), 0)

	// Vérifier qu'il y a des skips détectés (ou pas, selon le seuil)
	// Note: Les skips peuvent ne pas être détectés si le ratio est trop faible
	// C'est un comportement attendu basé sur le seuil de 0.1
	_ = result.Segments // Utilisé pour vérifier la structure
}

func TestPlaybackHeatmapService_GenerateHeatmap_IntensityNormalization(t *testing.T) {
	db, service := setupTestPlaybackHeatmapServiceDB(t)
	ctx := context.Background()

	// Créer user et track
	userID := uuid.New()
	trackID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Créer plusieurs analytics pour avoir des intensités variées
	now := time.Now()
	for i := 0; i < 5; i++ {
		analytics := &models.PlaybackAnalytics{
			TrackID:        trackID,
			UserID:         userID,
			PlayTime:       90 + (i * 10),
			PauseCount:     0,
			SeekCount:      0,
			CompletionRate: float64(50 + i*5),
			StartedAt:      now,
			CreatedAt:      now,
		}
		db.Create(analytics)
	}

	result, err := service.GenerateHeatmap(ctx, trackID, 10)

	require.NoError(t, err)
	assert.NotNil(t, result)

	// Vérifier que les intensités sont normalisées (0-1)
	for _, seg := range result.Segments {
		assert.GreaterOrEqual(t, seg.Intensity, 0.0)
		assert.LessOrEqual(t, seg.Intensity, 1.0)
	}
}
