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
	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)

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

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Equal(t, trackID, 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, uuid.Nil)

	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, uuid.New())

	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
	user1ID := uuid.New()
	user2ID := uuid.New()
	trackID := uuid.New()
	user1 := &models.User{ID: user1ID, Username: "user1", Slug: "user1", Email: "user1@example.com", IsActive: true}
	user2 := &models.User{ID: user2ID, Username: "user2", Slug: "user2", Email: "user2@example.com", IsActive: true}
	db.Create(user1)
	db.Create(user2)
	track := &models.Track{
		ID:       trackID,
		UserID:   user1ID,
		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:        trackID,
		UserID:         user1ID,
		PlayTime:       180,
		PauseCount:     0,
		SeekCount:      0,
		CompletionRate: 100.0,
		StartedAt:      now,
		CreatedAt:      now,
	}
	analytics2 := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         user1ID,
		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:        trackID,
		UserID:         user2ID,
		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, trackID)

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Equal(t, trackID, 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)

	user1ID := uuid.New()
	user2ID := uuid.New()
	user3ID := uuid.New()
	userMetrics := map[uuid.UUID]*UserMetrics{
		user1ID: {UserID: user1ID, EngagementScore: 85.0}, // High
		user2ID: {UserID: user2ID, EngagementScore: 60.0}, // Medium
		user3ID: {UserID: user3ID, 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], user1ID)
	assert.Contains(t, segments[SegmentMediumEngagement], user2ID)
	assert.Contains(t, segments[SegmentLowEngagement], user3ID)
}

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

	user1ID := uuid.New()
	user2ID := uuid.New()
	user3ID := uuid.New()
	userMetrics := map[uuid.UUID]*UserMetrics{
		user1ID: {UserID: user1ID, AverageCompletion: 90.0}, // High
		user2ID: {UserID: user2ID, AverageCompletion: 60.0}, // Medium
		user3ID: {UserID: user3ID, 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], user1ID)
	assert.Contains(t, segments[SegmentMediumCompletion], user2ID)
	assert.Contains(t, segments[SegmentLowCompletion], user3ID)
}

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

	user1ID := uuid.New()
	user2ID := uuid.New()
	user3ID := uuid.New()
	user4ID := uuid.New()
	userMetrics := map[uuid.UUID]*UserMetrics{
		user1ID: {UserID: user1ID, SessionCount: 10, AverageSeeks: 0.5, AverageCompletion: 80.0}, // Active + Focused
		user2ID: {UserID: user2ID, SessionCount: 1, AverageSeeks: 0.2, AverageCompletion: 75.0},  // Casual + Focused
		user3ID: {UserID: user3ID, SessionCount: 5, AverageSeeks: 5.0, AverageCompletion: 50.0},  // Frequent skipper
		user4ID: {UserID: user4ID, 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)

	user1ID := uuid.New()
	user2ID := uuid.New()
	analytics := []models.PlaybackAnalytics{
		{UserID: user1ID, PlayTime: 180, PauseCount: 0, SeekCount: 0, CompletionRate: 100.0},
		{UserID: user1ID, PlayTime: 180, PauseCount: 1, SeekCount: 0, CompletionRate: 95.0},
		{UserID: user2ID, PlayTime: 45, PauseCount: 5, SeekCount: 3, CompletionRate: 25.0},
	}

	userMetrics := service.calculateUserMetrics(analytics)

	assert.Equal(t, 2, len(userMetrics))
	assert.Contains(t, userMetrics, user1ID)
	assert.Contains(t, userMetrics, user2ID)

	// Vérifier les métriques de l'utilisateur 1
	metrics1 := userMetrics[user1ID]
	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[user2ID]
	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
	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 analytics avec high engagement
	now := time.Now()
	analytics := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       180,
		PauseCount:     0,
		SeekCount:      0,
		CompletionRate: 100.0,
		StartedAt:      now,
		CreatedAt:      now,
	}
	db.Create(analytics)

	segment, err := service.GetUserSegment(ctx, trackID, userID)

	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, uuid.Nil, uuid.New())
	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, uuid.New(), uuid.Nil)
	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
	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)

	unknownUserID := uuid.New()
	segment, err := service.GetUserSegment(ctx, trackID, unknownUserID)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "user "+unknownUserID.String()+" 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
	user1ID := uuid.New()
	user2ID := uuid.New()
	user3ID := uuid.New()
	user4ID := uuid.New()
	users := []*models.User{
		{ID: user1ID, Username: "user1", Slug: "user1", Email: "user1@example.com", IsActive: true},
		{ID: user2ID, Username: "user2", Slug: "user2", Email: "user2@example.com", IsActive: true},
		{ID: user3ID, Username: "user3", Slug: "user3", Email: "user3@example.com", IsActive: true},
		{ID: user4ID, Username: "user4", Slug: "user4", Email: "user4@example.com", IsActive: true},
	}
	for _, u := range users {
		db.Create(u)
	}

	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   user1ID,
		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:        trackID,
			UserID:         user1ID,
			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:        trackID,
		UserID:         user2ID,
		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:        trackID,
			UserID:         user3ID,
			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:        trackID,
		UserID:         user4ID,
		PlayTime:       180,
		PauseCount:     0,
		SeekCount:      0,
		CompletionRate: 100.0,
		StartedAt:      now,
		CreatedAt:      now,
	})

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

	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))
}
