package metrics

import (
	"sync"
	"time"

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

// TimeWindow représente une fenêtre de temps avec des métriques agrégées
type TimeWindow struct {
	Start              time.Time                  `json:"start"`
	End                time.Time                  `json:"end"`
	Errors             int64                      `json:"errors"`
	Requests           int64                      `json:"requests"`
	ErrorsByCode       map[errors.ErrorCode]int64 `json:"errors_by_code"`
	ErrorsByHTTPStatus map[int]int64              `json:"errors_by_http_status"`
}

// AggregatedMetrics gère l'agrégation des métriques sur des fenêtres de temps
type AggregatedMetrics struct {
	mu      sync.RWMutex
	windows map[string][]TimeWindow // key: "1m", "5m", "1h"

	// Configuration des fenêtres en secondes
	windowSizes map[string]time.Duration
	maxWindows  map[string]int // Nombre max de fenêtres à garder par type
}

// NewAggregatedMetrics crée une nouvelle instance de AggregatedMetrics
func NewAggregatedMetrics() *AggregatedMetrics {
	agg := &AggregatedMetrics{
		windows: make(map[string][]TimeWindow),
		windowSizes: map[string]time.Duration{
			"1m": 1 * time.Minute,
			"5m": 5 * time.Minute,
			"1h": 1 * time.Hour,
		},
		maxWindows: map[string]int{
			"1m": 60, // Garder 60 fenêtres de 1 minute = 1 heure
			"5m": 12, // Garder 12 fenêtres de 5 minutes = 1 heure
			"1h": 24, // Garder 24 fenêtres de 1 heure = 24 heures
		},
	}

	// Démarrer la routine de nettoyage
	go agg.cleanupRoutine()

	return agg
}

// AddError enregistre une erreur dans les fenêtres d'agrégation
func (a *AggregatedMetrics) AddError(windowType string, code errors.ErrorCode, httpStatus int) {
	a.mu.Lock()
	defer a.mu.Unlock()

	now := time.Now()

	// Initialiser la fenêtre si elle n'existe pas
	if _, exists := a.windows[windowType]; !exists {
		a.windows[windowType] = []TimeWindow{}
	}

	windowSize, ok := a.windowSizes[windowType]
	if !ok {
		// Fenêtre non supportée
		return
	}

	// Trouver ou créer la fenêtre active
	windowStart := now.Truncate(windowSize)
	windowEnd := windowStart.Add(windowSize)

	// Chercher la fenêtre active
	found := false
	for i := range a.windows[windowType] {
		if a.windows[windowType][i].Start.Equal(windowStart) {
			// Fenêtre existante - mettre à jour
			a.windows[windowType][i].Errors++
			a.windows[windowType][i].ErrorsByCode[code]++
			a.windows[windowType][i].ErrorsByHTTPStatus[httpStatus]++
			found = true
			break
		}
	}

	if !found {
		// Créer une nouvelle fenêtre
		newWindow := TimeWindow{
			Start:              windowStart,
			End:                windowEnd,
			Errors:             1,
			Requests:           0,
			ErrorsByCode:       make(map[errors.ErrorCode]int64),
			ErrorsByHTTPStatus: make(map[int]int64),
		}
		newWindow.ErrorsByCode[code] = 1
		newWindow.ErrorsByHTTPStatus[httpStatus] = 1
		a.windows[windowType] = append(a.windows[windowType], newWindow)
	}

	// Nettoyer les anciennes fenêtres (garder seulement les plus récentes)
	a.cleanupWindows(windowType)
}

// AddRequest enregistre une requête dans les fenêtres d'agrégation
func (a *AggregatedMetrics) AddRequest(windowType string) {
	a.mu.Lock()
	defer a.mu.Unlock()

	now := time.Now()

	// Initialiser la fenêtre si elle n'existe pas
	if _, exists := a.windows[windowType]; !exists {
		a.windows[windowType] = []TimeWindow{}
	}

	windowSize, ok := a.windowSizes[windowType]
	if !ok {
		return
	}

	// Trouver ou créer la fenêtre active
	windowStart := now.Truncate(windowSize)

	// Chercher la fenêtre active
	found := false
	for i := range a.windows[windowType] {
		if a.windows[windowType][i].Start.Equal(windowStart) {
			a.windows[windowType][i].Requests++
			found = true
			break
		}
	}

	if !found {
		// Créer une nouvelle fenêtre
		newWindow := TimeWindow{
			Start:              windowStart,
			End:                windowStart.Add(windowSize),
			Errors:             0,
			Requests:           1,
			ErrorsByCode:       make(map[errors.ErrorCode]int64),
			ErrorsByHTTPStatus: make(map[int]int64),
		}
		a.windows[windowType] = append(a.windows[windowType], newWindow)
	}

	// Nettoyer les anciennes fenêtres
	a.cleanupWindows(windowType)
}

// GetAggregated retourne les métriques agrégées pour un type de fenêtre
func (a *AggregatedMetrics) GetAggregated(windowType string) []TimeWindow {
	a.mu.RLock()
	defer a.mu.RUnlock()

	if windows, exists := a.windows[windowType]; exists {
		// Retourner une copie pour éviter les modifications concurrentes
		result := make([]TimeWindow, len(windows))
		for i, w := range windows {
			result[i] = w
			// Copier les maps
			result[i].ErrorsByCode = make(map[errors.ErrorCode]int64)
			result[i].ErrorsByHTTPStatus = make(map[int]int64)
			for k, v := range w.ErrorsByCode {
				result[i].ErrorsByCode[k] = v
			}
			for k, v := range w.ErrorsByHTTPStatus {
				result[i].ErrorsByHTTPStatus[k] = v
			}
		}
		return result
	}

	return []TimeWindow{}
}

// GetAllAggregated retourne toutes les métriques agrégées
func (a *AggregatedMetrics) GetAllAggregated() map[string][]TimeWindow {
	a.mu.RLock()
	defer a.mu.RUnlock()

	result := make(map[string][]TimeWindow)
	for windowType := range a.windows {
		result[windowType] = a.GetAggregated(windowType)
	}

	return result
}

// cleanupWindows nettoie les anciennes fenêtres pour un type donné
func (a *AggregatedMetrics) cleanupWindows(windowType string) {
	max, ok := a.maxWindows[windowType]
	if !ok {
		return
	}

	if len(a.windows[windowType]) <= max {
		return
	}

	// Garder seulement les fenêtres les plus récentes
	windows := a.windows[windowType]

	// Trier par date (les plus récentes en premier)
	// Les fenêtres sont normalement déjà ordonnées, mais on s'assure
	// On garde les max dernières
	if len(windows) > max {
		startIdx := len(windows) - max
		a.windows[windowType] = windows[startIdx:]
	}
}

// cleanupRoutine nettoie périodiquement les anciennes fenêtres
func (a *AggregatedMetrics) cleanupRoutine() {
	ticker := time.NewTicker(1 * time.Minute) // Nettoyer chaque minute
	defer ticker.Stop()

	for range ticker.C {
		a.mu.Lock()

		now := time.Now()

		// Nettoyer les fenêtres expirées pour chaque type
		for windowType, windows := range a.windows {
			windowSize := a.windowSizes[windowType]
			maxAge := windowSize * time.Duration(a.maxWindows[windowType])

			validWindows := []TimeWindow{}
			for _, w := range windows {
				// Garder les fenêtres qui ne sont pas trop anciennes
				if now.Sub(w.End) < maxAge {
					validWindows = append(validWindows, w)
				}
			}

			a.windows[windowType] = validWindows
		}

		a.mu.Unlock()
	}
}
