package services

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/google/uuid" // Added import
	"go.uber.org/zap"
	"gorm.io/gorm"
)

// PlaybackAnalyticsRateLimiter gère le rate limiting pour les analytics de playback
// T0389: Create Playback Analytics Rate Limiting
type PlaybackAnalyticsRateLimiter struct {
	db     *gorm.DB
	logger *zap.Logger

	// Rate limiting par utilisateur (requêtes par minute)
	requestsPerMinute int
	requestsWindow    time.Duration

	// Throttling (délai minimum entre requêtes)
	minRequestInterval time.Duration

	// Quotas (limites quotidiennes et hebdomadaires)
	dailyQuota  int
	weeklyQuota int

	// Cache en mémoire pour le rate limiting
	mu              sync.RWMutex
	userRequests    map[uuid.UUID][]time.Time // userID -> []time.Time
	userLastRequest map[uuid.UUID]time.Time   // userID -> last request time
	userDailyCount  map[uuid.UUID]int         // userID -> daily count
	userWeeklyCount map[uuid.UUID]int         // userID -> weekly count
	lastCleanup     time.Time
}

// RateLimitConfig configuration pour le rate limiter
// T0389: Create Playback Analytics Rate Limiting
type RateLimitConfig struct {
	RequestsPerMinute  int           // Nombre de requêtes par minute
	RequestsWindow     time.Duration // Fenêtre de temps pour les requêtes
	MinRequestInterval time.Duration // Délai minimum entre requêtes (throttling)
	DailyQuota         int           // Quota quotidien
	WeeklyQuota        int           // Quota hebdomadaire
}

// DefaultRateLimitConfig retourne une configuration par défaut
func DefaultRateLimitConfig() RateLimitConfig {
	return RateLimitConfig{
		RequestsPerMinute:  60,              // 60 requêtes par minute
		RequestsWindow:     1 * time.Minute, // Fenêtre de 1 minute
		MinRequestInterval: 1 * time.Second, // Minimum 1 seconde entre requêtes
		DailyQuota:         10000,           // 10000 analytics par jour
		WeeklyQuota:        50000,           // 50000 analytics par semaine
	}
}

// NewPlaybackAnalyticsRateLimiter crée un nouveau rate limiter pour les analytics
// T0389: Create Playback Analytics Rate Limiting
func NewPlaybackAnalyticsRateLimiter(db *gorm.DB, logger *zap.Logger, config RateLimitConfig) *PlaybackAnalyticsRateLimiter {
	if logger == nil {
		logger = zap.NewNop()
	}

	limiter := &PlaybackAnalyticsRateLimiter{
		db:                 db,
		logger:             logger,
		requestsPerMinute:  config.RequestsPerMinute,
		requestsWindow:     config.RequestsWindow,
		minRequestInterval: config.MinRequestInterval,
		dailyQuota:         config.DailyQuota,
		weeklyQuota:        config.WeeklyQuota,
		userRequests:       make(map[uuid.UUID][]time.Time),
		userLastRequest:    make(map[uuid.UUID]time.Time),
		userDailyCount:     make(map[uuid.UUID]int),
		userWeeklyCount:    make(map[uuid.UUID]int),
		lastCleanup:        time.Now(),
	}

	// Démarrer le nettoyage périodique
	go limiter.cleanup()

	return limiter
}

// RateLimitResult représente le résultat d'une vérification de rate limit
// T0389: Create Playback Analytics Rate Limiting
type RateLimitResult struct {
	Allowed    bool
	Reason     string
	RetryAfter time.Duration
	Remaining  int
	QuotaUsed  int
	QuotaLimit int
}

// CheckRateLimit vérifie si une requête est autorisée selon les limites
// T0389: Create Playback Analytics Rate Limiting
func (rl *PlaybackAnalyticsRateLimiter) CheckRateLimit(ctx context.Context, userID uuid.UUID) (*RateLimitResult, error) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	now := time.Now()

	// Nettoyer périodiquement le cache
	if now.Sub(rl.lastCleanup) > 5*time.Minute {
		rl.cleanupLocked(now)
		rl.lastCleanup = now
	}

	// 1. Vérifier le throttling (délai minimum entre requêtes)
	if lastRequest, exists := rl.userLastRequest[userID]; exists {
		timeSinceLastRequest := now.Sub(lastRequest)
		if timeSinceLastRequest < rl.minRequestInterval {
			retryAfter := rl.minRequestInterval - timeSinceLastRequest
			return &RateLimitResult{
				Allowed:    false,
				Reason:     "throttling: request too soon",
				RetryAfter: retryAfter,
			}, nil
		}
	}

	// 2. Vérifier le rate limiting (requêtes par minute)
	cutoff := now.Add(-rl.requestsWindow)
	validRequests := []time.Time{}
	if requests, exists := rl.userRequests[userID]; exists {
		for _, reqTime := range requests {
			if reqTime.After(cutoff) {
				validRequests = append(validRequests, reqTime)
			}
		}
	}

	if len(validRequests) >= rl.requestsPerMinute {
		// Calculer le temps d'attente jusqu'à ce que la plus ancienne requête expire
		oldestRequest := validRequests[0]
		retryAfter := oldestRequest.Add(rl.requestsWindow).Sub(now)
		if retryAfter < 0 {
			retryAfter = 0
		}

		return &RateLimitResult{
			Allowed:    false,
			Reason:     fmt.Sprintf("rate limit exceeded: %d requests per %v", rl.requestsPerMinute, rl.requestsWindow),
			RetryAfter: retryAfter,
			Remaining:  0,
		}, nil
	}

	// 3. Vérifier les quotas (quotas quotidiens et hebdomadaires)
	dailyCount, weeklyCount, err := rl.getQuotaCounts(ctx, userID, now)
	if err != nil {
		rl.logger.Warn("Failed to get quota counts, using cache",
			zap.Error(err),
			zap.String("user_id", userID.String())) // Utiliser les valeurs en cache en cas d'erreur
		dailyCount = rl.userDailyCount[userID]
		weeklyCount = rl.userWeeklyCount[userID]
	}

	if dailyCount >= rl.dailyQuota {
		return &RateLimitResult{
			Allowed:    false,
			Reason:     fmt.Sprintf("daily quota exceeded: %d/%d", dailyCount, rl.dailyQuota),
			RetryAfter: timeUntilMidnight(now),
			QuotaUsed:  dailyCount,
			QuotaLimit: rl.dailyQuota,
		}, nil
	}

	if weeklyCount >= rl.weeklyQuota {
		return &RateLimitResult{
			Allowed:    false,
			Reason:     fmt.Sprintf("weekly quota exceeded: %d/%d", weeklyCount, rl.weeklyQuota),
			RetryAfter: timeUntilNextWeek(now),
			QuotaUsed:  weeklyCount,
			QuotaLimit: rl.weeklyQuota,
		}, nil
	}

	// Toutes les vérifications passées, autoriser la requête
	validRequests = append(validRequests, now)
	rl.userRequests[userID] = validRequests
	rl.userLastRequest[userID] = now

	remaining := rl.requestsPerMinute - len(validRequests)

	return &RateLimitResult{
		Allowed:    true,
		Remaining:  remaining,
		QuotaUsed:  dailyCount,
		QuotaLimit: rl.dailyQuota,
	}, nil
}

// RecordRequest enregistre une requête (appelé après qu'une requête a été traitée avec succès)
// T0389: Create Playback Analytics Rate Limiting
func (rl *PlaybackAnalyticsRateLimiter) RecordRequest(ctx context.Context, userID uuid.UUID) error {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	// Mettre à jour les compteurs de quota
	rl.userDailyCount[userID]++
	rl.userWeeklyCount[userID]++

	// Enregistrer dans la base de données pour persistance
	// Note: On pourrait créer une table de quotas si nécessaire
	// Pour l'instant, on utilise uniquement le cache en mémoire

	return nil
}

// GetQuotaInfo retourne les informations de quota pour un utilisateur
// T0389: Create Playback Analytics Rate Limiting
func (rl *PlaybackAnalyticsRateLimiter) GetQuotaInfo(ctx context.Context, userID uuid.UUID) (map[string]interface{}, error) {
	rl.mu.RLock()
	defer rl.mu.RUnlock()

	now := time.Now()
	dailyCount, weeklyCount, err := rl.getQuotaCounts(ctx, userID, now)
	if err != nil {
		// Utiliser les valeurs en cache
		dailyCount = rl.userDailyCount[userID]
		weeklyCount = rl.userWeeklyCount[userID]
	}

	// Calculer les requêtes restantes dans la fenêtre actuelle
	cutoff := now.Add(-rl.requestsWindow)
	validRequests := []time.Time{}
	if requests, exists := rl.userRequests[userID]; exists {
		for _, reqTime := range requests {
			if reqTime.After(cutoff) {
				validRequests = append(validRequests, reqTime)
			}
		}
	}
	remainingRequests := rl.requestsPerMinute - len(validRequests)
	if remainingRequests < 0 {
		remainingRequests = 0
	}

	return map[string]interface{}{
		"rate_limit": map[string]interface{}{
			"requests_per_minute": rl.requestsPerMinute,
			"remaining":           remainingRequests,
			"window":              rl.requestsWindow.String(),
		},
		"throttling": map[string]interface{}{
			"min_interval": rl.minRequestInterval.String(),
		},
		"quotas": map[string]interface{}{
			"daily": map[string]interface{}{
				"used":      dailyCount,
				"limit":     rl.dailyQuota,
				"remaining": rl.dailyQuota - dailyCount,
			},
			"weekly": map[string]interface{}{
				"used":      weeklyCount,
				"limit":     rl.weeklyQuota,
				"remaining": rl.weeklyQuota - weeklyCount,
			},
		},
	}, nil
}

// getQuotaCounts récupère les compteurs de quota depuis la base de données
// T0389: Create Playback Analytics Rate Limiting
func (rl *PlaybackAnalyticsRateLimiter) getQuotaCounts(ctx context.Context, userID uuid.UUID, now time.Time) (int, int, error) {
	// Calculer les dates de début
	startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
	startOfWeek := startOfDay
	weekday := int(now.Weekday())
	if weekday == 0 {
		weekday = 7 // Dimanche = 7
	}
	startOfWeek = startOfWeek.AddDate(0, 0, -weekday+1) // Lundi

	// Compter les analytics enregistrées aujourd'hui
	var dailyCount int64
	err := rl.db.WithContext(ctx).
		Model(&struct {
			Count int64
		}{}).
		Select("COUNT(*)").
		Table("playback_analytics").
		Where("user_id = ? AND created_at >= ?", userID.String(), startOfDay).
		Scan(&dailyCount).Error
	if err != nil {
		return 0, 0, err
	}

	// Compter les analytics enregistrées cette semaine
	var weeklyCount int64
	err = rl.db.WithContext(ctx).
		Model(&struct {
			Count int64
		}{}).
		Select("COUNT(*)").
		Table("playback_analytics").
		Where("user_id = ? AND created_at >= ?", userID.String(), startOfWeek).
		Scan(&weeklyCount).Error
	if err != nil {
		return 0, 0, err
	}

	return int(dailyCount), int(weeklyCount), nil
}

// cleanup nettoie périodiquement le cache
// T0389: Create Playback Analytics Rate Limiting
func (rl *PlaybackAnalyticsRateLimiter) cleanup() {
	ticker := time.NewTicker(5 * time.Minute)
	defer ticker.Stop()

	for range ticker.C {
		rl.mu.Lock()
		rl.cleanupLocked(time.Now())
		rl.lastCleanup = time.Now()
		rl.mu.Unlock()
	}
}

// cleanupLocked nettoie le cache (doit être appelé avec le mutex verrouillé)
// T0389: Create Playback Analytics Rate Limiting
func (rl *PlaybackAnalyticsRateLimiter) cleanupLocked(now time.Time) {
	cutoff := now.Add(-rl.requestsWindow)

	// Nettoyer les requêtes expirées
	for userID, requests := range rl.userRequests {
		validRequests := []time.Time{}
		for _, reqTime := range requests {
			if reqTime.After(cutoff) {
				validRequests = append(validRequests, reqTime)
			}
		}
		if len(validRequests) == 0 {
			delete(rl.userRequests, userID)
		} else {
			rl.userRequests[userID] = validRequests
		}
	}

	// Nettoyer les dernières requêtes si trop anciennes
	cutoffLastRequest := now.Add(-1 * time.Hour)
	for userID, lastRequest := range rl.userLastRequest {
		if lastRequest.Before(cutoffLastRequest) {
			delete(rl.userLastRequest, userID)
		}
	}
}

// timeUntilMidnight calcule le temps jusqu'à minuit
func timeUntilMidnight(now time.Time) time.Duration {
	midnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
	return midnight.Sub(now)
}

// timeUntilNextWeek calcule le temps jusqu'au prochain lundi
func timeUntilNextWeek(now time.Time) time.Duration {
	weekday := int(now.Weekday())
	if weekday == 0 {
		weekday = 7 // Dimanche = 7
	}
	daysUntilMonday := 8 - weekday // Jours jusqu'au prochain lundi
	nextMonday := time.Date(now.Year(), now.Month(), now.Day()+daysUntilMonday, 0, 0, 0, 0, now.Location())
	return nextMonday.Sub(now)
}
