package services

import (
	"context"
	"testing"

	"github.com/google/uuid"
	"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 setupTestBitrateAdaptationServiceDB(t *testing.T) (*gorm.DB, uuid.UUID, uuid.UUID) {
	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.Track{}, &models.BitrateAdaptationLog{})
	require.NoError(t, err)

	userID := uuid.New()
	// Create test user
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	err = db.Create(user).Error
	require.NoError(t, err)

	// Create test track
	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track).Error
	require.NoError(t, err)

	return db, userID, trackID
}

func TestNewBitrateAdaptationService(t *testing.T) {
	db, _, _ := setupTestBitrateAdaptationServiceDB(t)
	logger := zaptest.NewLogger(t)
	bandwidthService := NewBandwidthDetectionService(logger)

	service := NewBitrateAdaptationService(db, bandwidthService, logger)

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

func TestNewBitrateAdaptationService_NilLogger(t *testing.T) {
	db, _, _ := setupTestBitrateAdaptationServiceDB(t)
	bandwidthService := NewBandwidthDetectionService(nil)

	service := NewBitrateAdaptationService(db, bandwidthService, nil)

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

func TestBitrateAdaptationService_AdaptBitrate_NoChange(t *testing.T) {
	db, userID, trackID := setupTestBitrateAdaptationServiceDB(t)
	logger := zaptest.NewLogger(t)
	bandwidthService := NewBandwidthDetectionService(logger)
	service := NewBitrateAdaptationService(db, bandwidthService, logger)
	ctx := context.Background()

	// Test avec bitrate qui ne change pas
	// Bande passante de 2 Mbps = 2097152 bps, avec buffer 20% = 1677 kbps disponible
	// Recommandation: 320 kbps
	// Current: 320 kbps, donc pas de changement
	newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 320, 2097152, 0.5)

	require.NoError(t, err)
	assert.Equal(t, 320, newBitrate)

	// Vérifier qu'aucun log n'a été créé
	var count int64
	db.Model(&models.BitrateAdaptationLog{}).Count(&count)
	assert.Equal(t, int64(0), count)
}

func TestBitrateAdaptationService_AdaptBitrate_Increase(t *testing.T) {
	db, userID, trackID := setupTestBitrateAdaptationServiceDB(t)
	logger := zaptest.NewLogger(t)
	bandwidthService := NewBandwidthDetectionService(logger)
	service := NewBitrateAdaptationService(db, bandwidthService, logger)
	ctx := context.Background()

	// Test avec augmentation de bitrate
	// Bande passante de 10 Mbps = 10485760 bps, avec buffer 20% = 8388 kbps disponible
	// Recommandation: 320 kbps
	// Current: 128 kbps, buffer: 0.5 (50%)
	newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 128, 10485760, 0.5)

	require.NoError(t, err)
	assert.Equal(t, 320, newBitrate)

	// Vérifier qu'un log a été créé
	var log models.BitrateAdaptationLog
	err = db.First(&log).Error
	require.NoError(t, err)
	assert.Equal(t, trackID, log.TrackID)
	assert.Equal(t, userID, log.UserID)
	assert.Equal(t, 128, log.OldBitrate)
	assert.Equal(t, 320, log.NewBitrate)
	assert.Equal(t, models.BitrateReasonNetworkFast, log.Reason)
	assert.NotNil(t, log.NetworkBandwidth)
}

func TestBitrateAdaptationService_AdaptBitrate_Decrease(t *testing.T) {
	db, userID, trackID := setupTestBitrateAdaptationServiceDB(t)
	logger := zaptest.NewLogger(t)
	bandwidthService := NewBandwidthDetectionService(logger)
	service := NewBitrateAdaptationService(db, bandwidthService, logger)
	ctx := context.Background()

	// Test avec diminution de bitrate
	// Bande passante de 300 kbps = 307200 bps, avec buffer 20% = 245 kbps disponible
	// Recommandation: 192 kbps
	// Current: 320 kbps, buffer: 0.5 (50%)
	newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 320, 307200, 0.5)

	require.NoError(t, err)
	assert.Equal(t, 192, newBitrate)

	// Vérifier qu'un log a été créé
	var log models.BitrateAdaptationLog
	err = db.First(&log).Error
	require.NoError(t, err)
	assert.Equal(t, 320, log.OldBitrate)
	assert.Equal(t, 192, log.NewBitrate)
	assert.Equal(t, models.BitrateReasonNetworkSlow, log.Reason)
}

func TestBitrateAdaptationService_AdaptBitrate_LowBuffer_PreventIncrease(t *testing.T) {
	db, userID, trackID := setupTestBitrateAdaptationServiceDB(t)
	logger := zaptest.NewLogger(t)
	bandwidthService := NewBandwidthDetectionService(logger)
	service := NewBitrateAdaptationService(db, bandwidthService, logger)
	ctx := context.Background()

	// Test avec buffer faible qui empêche l'augmentation
	// Bande passante de 10 Mbps = 10485760 bps, recommandation: 320 kbps
	// Current: 128 kbps, buffer: 0.15 (15% < 20%)
	// L'augmentation devrait être bloquée
	newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 128, 10485760, 0.15)

	require.NoError(t, err)
	assert.Equal(t, 128, newBitrate) // Pas d'augmentation

	// Vérifier qu'aucun log n'a été créé (pas de changement)
	var count int64
	db.Model(&models.BitrateAdaptationLog{}).Count(&count)
	assert.Equal(t, int64(0), count)
}

func TestBitrateAdaptationService_AdaptBitrate_VeryLowBuffer_ForceDecrease(t *testing.T) {
	db, userID, trackID := setupTestBitrateAdaptationServiceDB(t)
	logger := zaptest.NewLogger(t)
	bandwidthService := NewBandwidthDetectionService(logger)
	service := NewBitrateAdaptationService(db, bandwidthService, logger)
	ctx := context.Background()

	// Test avec buffer très faible qui force la diminution
	// Bande passante de 10 Mbps = 10485760 bps, recommandation: 320 kbps
	// Current: 320 kbps, buffer: 0.05 (5% < 10%)
	// La diminution devrait être forcée
	newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 320, 10485760, 0.05)

	require.NoError(t, err)
	assert.Equal(t, 192, newBitrate) // Diminution forcée

	// Vérifier qu'un log a été créé
	var log models.BitrateAdaptationLog
	err = db.First(&log).Error
	require.NoError(t, err)
	assert.Equal(t, 320, log.OldBitrate)
	assert.Equal(t, 192, log.NewBitrate)
	assert.Equal(t, models.BitrateReasonBufferLow, log.Reason)
}

func TestBitrateAdaptationService_AdaptBitrate_VeryLowBuffer_192to128(t *testing.T) {
	db, userID, trackID := setupTestBitrateAdaptationServiceDB(t)
	logger := zaptest.NewLogger(t)
	bandwidthService := NewBandwidthDetectionService(logger)
	service := NewBitrateAdaptationService(db, bandwidthService, logger)
	ctx := context.Background()

	// Test avec buffer très faible, passage de 192 à 128
	newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 192, 10485760, 0.05)

	require.NoError(t, err)
	assert.Equal(t, 128, newBitrate)

	// Vérifier qu'un log a été créé
	var log models.BitrateAdaptationLog
	err = db.First(&log).Error
	require.NoError(t, err)
	assert.Equal(t, 192, log.OldBitrate)
	assert.Equal(t, 128, log.NewBitrate)
}

func TestBitrateAdaptationService_AdaptBitrate_InvalidParameters(t *testing.T) {
	db, userID, trackID := setupTestBitrateAdaptationServiceDB(t)
	logger := zaptest.NewLogger(t)
	bandwidthService := NewBandwidthDetectionService(logger)
	service := NewBitrateAdaptationService(db, bandwidthService, logger)
	ctx := context.Background()

	// Test avec trackID invalide
	_, err := service.AdaptBitrate(ctx, uuid.Nil, userID, 128, 10485760, 0.5)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid track ID")

	// Test avec userID invalide
	_, err = service.AdaptBitrate(ctx, trackID, uuid.Nil, 128, 10485760, 0.5)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid user ID")

	// Test avec currentBitrate invalide
	_, err = service.AdaptBitrate(ctx, trackID, userID, 0, 10485760, 0.5)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid current bitrate")

	// Test avec bufferLevel invalide (négatif)
	_, err = service.AdaptBitrate(ctx, trackID, userID, 128, 10485760, -0.1)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid buffer level")

	// Test avec bufferLevel invalide (> 1.0)
	_, err = service.AdaptBitrate(ctx, trackID, userID, 128, 10485760, 1.5)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid buffer level")
}

func TestBitrateAdaptationService_DetermineReason(t *testing.T) {
	db, _, _ := setupTestBitrateAdaptationServiceDB(t)
	logger := zaptest.NewLogger(t)
	bandwidthService := NewBandwidthDetectionService(logger)
	service := NewBitrateAdaptationService(db, bandwidthService, logger)

	// Test avec buffer faible
	reason := service.determineReason(128, 320, 0.15)
	assert.Equal(t, models.BitrateReasonBufferLow, reason)

	// Test avec augmentation (buffer normal)
	reason = service.determineReason(128, 320, 0.5)
	assert.Equal(t, models.BitrateReasonNetworkFast, reason)

	// Test avec diminution (buffer normal)
	reason = service.determineReason(320, 192, 0.5)
	assert.Equal(t, models.BitrateReasonNetworkSlow, reason)

	// Test avec buffer faible mais augmentation
	reason = service.determineReason(128, 192, 0.15)
	assert.Equal(t, models.BitrateReasonBufferLow, reason)
}

func TestBitrateAdaptationService_AdaptBitrate_MultipleAdaptations(t *testing.T) {
	db, userID, trackID := setupTestBitrateAdaptationServiceDB(t)
	logger := zaptest.NewLogger(t)
	bandwidthService := NewBandwidthDetectionService(logger)
	service := NewBitrateAdaptationService(db, bandwidthService, logger)
	ctx := context.Background()

	// Première adaptation: 128 -> 192
	// Bande passante de 300 kbps = 307200 bps, avec buffer 20% = 245 kbps disponible
	// Recommandation: 192 kbps
	newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 128, 307200, 0.5)
	require.NoError(t, err)
	assert.Equal(t, 192, newBitrate)

	// Deuxième adaptation: 192 -> 320
	// Bande passante de 10 Mbps = 10485760 bps, avec buffer 20% = 8388 kbps disponible
	// Recommandation: 320 kbps
	newBitrate, err = service.AdaptBitrate(ctx, trackID, userID, 192, 10485760, 0.5)
	require.NoError(t, err)
	assert.Equal(t, 320, newBitrate)

	// Vérifier qu'il y a 2 logs
	var count int64
	db.Model(&models.BitrateAdaptationLog{}).Count(&count)
	assert.Equal(t, int64(2), count)
}

func TestBitrateAdaptationService_AdaptBitrate_EdgeCases(t *testing.T) {
	db, userID, trackID := setupTestBitrateAdaptationServiceDB(t)
	logger := zaptest.NewLogger(t)
	bandwidthService := NewBandwidthDetectionService(logger)
	service := NewBitrateAdaptationService(db, bandwidthService, logger)
	ctx := context.Background()

	// Test avec buffer exactement à 20%
	newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 128, 10485760, 0.2)
	require.NoError(t, err)
	// À 20%, l'augmentation devrait être permise
	assert.Equal(t, 320, newBitrate)

	// Nettoyer les logs précédents
	db.Exec("DELETE FROM bitrate_adaptation_logs")

	// Test avec buffer exactement à 10%
	newBitrate, err = service.AdaptBitrate(ctx, trackID, userID, 320, 10485760, 0.1)
	require.NoError(t, err)
	// À 10%, la diminution devrait être forcée
	assert.Equal(t, 192, newBitrate)

	// Nettoyer les logs précédents
	db.Exec("DELETE FROM bitrate_adaptation_logs")

	// Test avec buffer à 0%
	newBitrate, err = service.AdaptBitrate(ctx, trackID, userID, 320, 10485760, 0.0)
	require.NoError(t, err)
	assert.Equal(t, 192, newBitrate)

	// Nettoyer les logs précédents
	db.Exec("DELETE FROM bitrate_adaptation_logs")

	// Test avec buffer à 100%
	newBitrate, err = service.AdaptBitrate(ctx, trackID, userID, 128, 10485760, 1.0)
	require.NoError(t, err)
	assert.Equal(t, 320, newBitrate)
}

func TestBitrateAdaptationService_AdaptBitrate_LogCreationFailure(t *testing.T) {
	// Créer une DB qui va échouer lors de la création
	// On utilise une table qui n'existe pas pour simuler l'erreur
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Ne pas créer la table bitrate_adaptation_logs pour simuler une erreur
	// Mais on doit créer User et Track pour que les foreign keys fonctionnent
	err = db.AutoMigrate(&models.User{}, &models.Track{})
	require.NoError(t, err)

	userID := uuid.New()
	// Create test user
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	require.NoError(t, db.Create(user).Error)

	// Create test track
	track := &models.Track{
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	require.NoError(t, db.Create(track).Error)

	logger := zaptest.NewLogger(t)
	bandwidthService := NewBandwidthDetectionService(logger)
	service := NewBitrateAdaptationService(db, bandwidthService, logger)
	ctx := context.Background()

	// L'adaptation devrait quand même fonctionner même si le log échoue
	newBitrate, err := service.AdaptBitrate(ctx, track.ID, userID, 128, 10485760, 0.5)

	// L'adaptation ne devrait pas retourner d'erreur même si le log échoue
	require.NoError(t, err)
	assert.Equal(t, 320, newBitrate)
}
