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 setupTestPlaybackSegmentationServiceDB(t *testing.T) (*gorm.DB, *PlaybackSegmentationService) {
	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 := NewPlaybackSegmentationService(db, logger)

	return db, service
}

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

	service := NewPlaybackSegmentationService(db, logger)

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

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

	service := NewPlaybackSegmentationService(db, nil)

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

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

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

	result, err := service.SegmentUsers(ctx, 1)

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Equal(t, int64(1), result.TrackID)
	assert.Equal(t, int64(0), result.TotalUsers)
	assert.NotNil(t, result.Segments)
	assert.NotNil(t, result.UserMetrics)
}

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

	result, err := service.SegmentUsers(ctx, 0)

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

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

	result, err := service.SegmentUsers(ctx, 999)

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

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

	// Créer users et track
	user1 := &models.User{ID: 1, Username: "user1", Slug: "user1", Email: "user1@example.com", IsActive: true}
	user2 := &models.User{ID: 2, Username: "user2", Slug: "user2", Email: "user2@example.com", IsActive: true}
	db.Create(user1)
	db.Create(user2)
	track := &models.Track{
		ID:       1,
		UserID:   1,
		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 différents niveaux d'engagement
	now := time.Now()
	// User 1: High engagement (completion élevé, peu de pauses/seeks)
	analytics1 := &models.PlaybackAnalytics{
		TrackID:        1,
		UserID:         1,
		PlayTime:       180,
		PauseCount:     0,
		SeekCount:      0,
		CompletionRate: 100.0,
		StartedAt:      now,
		CreatedAt:      now,
	}
	analytics2 := &models.PlaybackAnalytics{
		TrackID:        1,
		UserID:         1,
		PlayTime:       180,
		PauseCount:     1,
		SeekCount:      0,
		CompletionRate: 95.0,
		StartedAt:      now,
		CreatedAt:      now,
	}
	// User 2: Low engagement (completion faible, beaucoup de pauses/seeks)
	analytics3 := &models.PlaybackAnalytics{
		TrackID:        1,
		UserID:         2,
		PlayTime:       45,
		PauseCount:     5,
		SeekCount:      3,
		CompletionRate: 25.0,
		StartedAt:      now,
		CreatedAt:      now,
	}
	db.Create(analytics1)
	db.Create(analytics2)
	db.Create(analytics3)

	result, err := service.SegmentUsers(ctx, 1)

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Equal(t, int64(1), result.TrackID)
	assert.Equal(t, int64(2), result.TotalUsers)
	assert.NotNil(t, result.Segments)
	assert.Greater(t, len(result.Segments), 0)

	// Vérifier que les segments sont créés
	assert.Contains(t, result.Segments, SegmentHighEngagement)
	assert.Contains(t, result.Segments, SegmentLowEngagement)
}

func TestPlaybackSegmentationService_SegmentByEngagement(t *testing.T) {
	_, service := setupTestPlaybackSegmentationServiceDB(t)

	userMetrics := map[int64]*UserMetrics{
		1: {UserID: 1, EngagementScore: 85.0}, // High
		2: {UserID: 2, EngagementScore: 60.0}, // Medium
		3: {UserID: 3, EngagementScore: 30.0}, // Low
	}

	segments := service.segmentByEngagement(userMetrics)

	assert.Contains(t, segments, SegmentHighEngagement)
	assert.Contains(t, segments, SegmentMediumEngagement)
	assert.Contains(t, segments, SegmentLowEngagement)
	assert.Contains(t, segments[SegmentHighEngagement], int64(1))
	assert.Contains(t, segments[SegmentMediumEngagement], int64(2))
	assert.Contains(t, segments[SegmentLowEngagement], int64(3))
}

func TestPlaybackSegmentationService_SegmentByCompletionRate(t *testing.T) {
	_, service := setupTestPlaybackSegmentationServiceDB(t)

	userMetrics := map[int64]*UserMetrics{
		1: {UserID: 1, AverageCompletion: 90.0}, // High
		2: {UserID: 2, AverageCompletion: 60.0}, // Medium
		3: {UserID: 3, AverageCompletion: 30.0}, // Low
	}

	segments := service.segmentByCompletionRate(userMetrics)

	assert.Contains(t, segments, SegmentHighCompletion)
	assert.Contains(t, segments, SegmentMediumCompletion)
	assert.Contains(t, segments, SegmentLowCompletion)
	assert.Contains(t, segments[SegmentHighCompletion], int64(1))
	assert.Contains(t, segments[SegmentMediumCompletion], int64(2))
	assert.Contains(t, segments[SegmentLowCompletion], int64(3))
}

func TestPlaybackSegmentationService_SegmentByBehavior(t *testing.T) {
	_, service := setupTestPlaybackSegmentationServiceDB(t)

	userMetrics := map[int64]*UserMetrics{
		1: {UserID: 1, SessionCount: 10, AverageSeeks: 0.5, AverageCompletion: 80.0}, // Active + Focused
		2: {UserID: 2, SessionCount: 1, AverageSeeks: 0.2, AverageCompletion: 75.0},  // Casual + Focused
		3: {UserID: 3, SessionCount: 5, AverageSeeks: 5.0, AverageCompletion: 50.0},  // Frequent skipper
		4: {UserID: 4, SessionCount: 2, AverageSeeks: 0.1, AverageCompletion: 60.0},  // Casual
	}

	segments := service.segmentByBehavior(userMetrics)

	assert.Contains(t, segments, SegmentActiveListener)
	assert.Contains(t, segments, SegmentCasualListener)
	assert.Contains(t, segments, SegmentFrequentSkipper)
	assert.Contains(t, segments, SegmentFocusedListener)
}

func TestPlaybackSegmentationService_CalculateUserMetrics(t *testing.T) {
	_, service := setupTestPlaybackSegmentationServiceDB(t)

	analytics := []models.PlaybackAnalytics{
		{UserID: 1, PlayTime: 180, PauseCount: 0, SeekCount: 0, CompletionRate: 100.0},
		{UserID: 1, PlayTime: 180, PauseCount: 1, SeekCount: 0, CompletionRate: 95.0},
		{UserID: 2, PlayTime: 45, PauseCount: 5, SeekCount: 3, CompletionRate: 25.0},
	}

	userMetrics := service.calculateUserMetrics(analytics)

	assert.Equal(t, 2, len(userMetrics))
	assert.Contains(t, userMetrics, int64(1))
	assert.Contains(t, userMetrics, int64(2))

	// Vérifier les métriques de l'utilisateur 1
	metrics1 := userMetrics[1]
	assert.Equal(t, int64(2), metrics1.SessionCount)
	assert.InDelta(t, 97.5, metrics1.AverageCompletion, 0.1) // (100 + 95) / 2
	assert.InDelta(t, 180.0, metrics1.AveragePlayTime, 0.1)
	assert.InDelta(t, 0.5, metrics1.AveragePauses, 0.1) // (0 + 1) / 2 = 0.5
	assert.Equal(t, 0.0, metrics1.AverageSeeks)
	assert.Greater(t, metrics1.EngagementScore, 75.0) // High engagement

	// Vérifier les métriques de l'utilisateur 2
	metrics2 := userMetrics[2]
	assert.Equal(t, int64(1), metrics2.SessionCount)
	assert.Equal(t, 25.0, metrics2.AverageCompletion)
	assert.Equal(t, 5.0, metrics2.AveragePauses)
	assert.Equal(t, 3.0, metrics2.AverageSeeks)
	assert.Less(t, metrics2.EngagementScore, 50.0) // Low engagement
}

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

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

	// Créer analytics avec high engagement
	now := time.Now()
	analytics := &models.PlaybackAnalytics{
		TrackID:        1,
		UserID:         1,
		PlayTime:       180,
		PauseCount:     0,
		SeekCount:      0,
		CompletionRate: 100.0,
		StartedAt:      now,
		CreatedAt:      now,
	}
	db.Create(analytics)

	segment, err := service.GetUserSegment(ctx, 1, 1)

	require.NoError(t, err)
	assert.Equal(t, SegmentHighEngagement, segment)
}

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

	segment, err := service.GetUserSegment(ctx, 0, 1)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid track ID or user ID")
	assert.Equal(t, UserSegment(""), segment)

	segment, err = service.GetUserSegment(ctx, 1, 0)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid track ID or user ID")
	assert.Equal(t, UserSegment(""), segment)
}

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

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

	segment, err := service.GetUserSegment(ctx, 1, 999)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "user 999 not found")
	assert.Equal(t, UserSegment(""), segment)
}

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

	// Créer plusieurs users avec différents comportements
	users := []*models.User{
		{ID: 1, Username: "user1", Slug: "user1", Email: "user1@example.com", IsActive: true},
		{ID: 2, Username: "user2", Slug: "user2", Email: "user2@example.com", IsActive: true},
		{ID: 3, Username: "user3", Slug: "user3", Email: "user3@example.com", IsActive: true},
		{ID: 4, Username: "user4", Slug: "user4", Email: "user4@example.com", IsActive: true},
	}
	for _, u := range users {
		db.Create(u)
	}

	track := &models.Track{
		ID:       1,
		UserID:   1,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	now := time.Now()
	// User 1: High engagement, high completion, active, focused
	for i := 0; i < 5; i++ {
		db.Create(&models.PlaybackAnalytics{
			TrackID:        1,
			UserID:         1,
			PlayTime:       180,
			PauseCount:     0,
			SeekCount:      0,
			CompletionRate: 100.0,
			StartedAt:      now,
			CreatedAt:      now,
		})
	}

	// User 2: Medium engagement, medium completion, casual
	db.Create(&models.PlaybackAnalytics{
		TrackID:        1,
		UserID:         2,
		PlayTime:       90,
		PauseCount:     2,
		SeekCount:      1,
		CompletionRate: 50.0,
		StartedAt:      now,
		CreatedAt:      now,
	})

	// User 3: Low engagement, low completion, frequent skipper
	for i := 0; i < 3; i++ {
		db.Create(&models.PlaybackAnalytics{
			TrackID:        1,
			UserID:         3,
			PlayTime:       30,
			PauseCount:     5,
			SeekCount:      5,
			CompletionRate: 15.0,
			StartedAt:      now,
			CreatedAt:      now,
		})
	}

	// User 4: High engagement, high completion, casual
	db.Create(&models.PlaybackAnalytics{
		TrackID:        1,
		UserID:         4,
		PlayTime:       180,
		PauseCount:     0,
		SeekCount:      0,
		CompletionRate: 100.0,
		StartedAt:      now,
		CreatedAt:      now,
	})

	result, err := service.SegmentUsers(ctx, 1)

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Equal(t, int64(4), result.TotalUsers)

	// Vérifier que tous les segments sont présents
	assert.Contains(t, result.Segments, SegmentHighEngagement)
	assert.Contains(t, result.Segments, SegmentMediumEngagement)
	assert.Contains(t, result.Segments, SegmentLowEngagement)
	assert.Contains(t, result.Segments, SegmentHighCompletion)
	assert.Contains(t, result.Segments, SegmentMediumCompletion)
	assert.Contains(t, result.Segments, SegmentLowCompletion)
	assert.Contains(t, result.Segments, SegmentActiveListener)
	assert.Contains(t, result.Segments, SegmentCasualListener)
	assert.Contains(t, result.Segments, SegmentFrequentSkipper)
	assert.Contains(t, result.Segments, SegmentFocusedListener)

	// Vérifier les compteurs
	assert.Greater(t, result.SegmentCounts[SegmentHighEngagement], int64(0))
	assert.Greater(t, result.SegmentCounts[SegmentLowEngagement], int64(0))
}
