package dev.lions.unionflow.client.service;

import jakarta.enterprise.context.ApplicationScoped;
import org.jboss.logging.Logger;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Service de monitoring et métriques pour la production
 * 
 * Collecte et expose des métriques applicatives :
 * - Compteurs d'appels backend
 * - Temps de réponse (min, max, moyenne)
 * - Taux d'erreurs
 * - Utilisation du cache
 * - Statistiques de retry
 * 
 * @author UnionFlow Team
 * @version 3.0
 * @since 2026-01-04
 */
@ApplicationScoped
public class MetricsService {
    
    private static final Logger LOG = Logger.getLogger(MetricsService.class);
    
    // Compteurs d'appels backend
    private final Map<String, AtomicLong> backendCallCounters = new ConcurrentHashMap<>();
    private final Map<String, AtomicLong> backendSuccessCounters = new ConcurrentHashMap<>();
    private final Map<String, AtomicLong> backendErrorCounters = new ConcurrentHashMap<>();
    
    // Temps de réponse
    private final Map<String, ResponseTimeStats> responseTimeStats = new ConcurrentHashMap<>();
    
    // Cache metrics
    private final AtomicLong cacheHits = new AtomicLong(0);
    private final AtomicLong cacheMisses = new AtomicLong(0);
    
    // Retry metrics
    private final AtomicLong retryAttempts = new AtomicLong(0);
    private final AtomicLong retrySuccesses = new AtomicLong(0);
    private final AtomicLong retryFailures = new AtomicLong(0);
    
    // Validation metrics
    private final AtomicLong validationSuccesses = new AtomicLong(0);
    private final AtomicLong validationFailures = new AtomicLong(0);
    
    // Timestamp de démarrage
    private final Instant startTime = Instant.now();
    
    /**
     * Enregistre un appel backend
     */
    public void recordBackendCall(String serviceName, boolean success, long durationMs) {
        backendCallCounters.computeIfAbsent(serviceName, k -> new AtomicLong(0)).incrementAndGet();
        
        if (success) {
            backendSuccessCounters.computeIfAbsent(serviceName, k -> new AtomicLong(0)).incrementAndGet();
        } else {
            backendErrorCounters.computeIfAbsent(serviceName, k -> new AtomicLong(0)).incrementAndGet();
        }
        
        // Enregistrer le temps de réponse
        ResponseTimeStats stats = responseTimeStats.computeIfAbsent(serviceName, k -> new ResponseTimeStats());
        stats.record(durationMs);
        
        LOG.debugf("Métrique backend: %s - success=%b, durée=%dms", serviceName, success, durationMs);
    }
    
    /**
     * Enregistre un hit de cache
     */
    public void recordCacheHit() {
        cacheHits.incrementAndGet();
    }
    
    /**
     * Enregistre un miss de cache
     */
    public void recordCacheMiss() {
        cacheMisses.incrementAndGet();
    }
    
    /**
     * Enregistre une tentative de retry
     */
    public void recordRetryAttempt() {
        retryAttempts.incrementAndGet();
    }
    
    /**
     * Enregistre un retry réussi
     */
    public void recordRetrySuccess() {
        retrySuccesses.incrementAndGet();
    }
    
    /**
     * Enregistre un retry échoué
     */
    public void recordRetryFailure() {
        retryFailures.incrementAndGet();
    }
    
    /**
     * Enregistre une validation
     */
    public void recordValidation(boolean success) {
        if (success) {
            validationSuccesses.incrementAndGet();
        } else {
            validationFailures.incrementAndGet();
        }
    }
    
    /**
     * Obtient les métriques globales
     */
    public Map<String, Object> getGlobalMetrics() {
        Map<String, Object> metrics = new HashMap<>();
        
        // Uptime
        Duration uptime = Duration.between(startTime, Instant.now());
        metrics.put("uptime_seconds", uptime.getSeconds());
        metrics.put("uptime_hours", uptime.toHours());
        
        // Backend calls
        long totalCalls = backendCallCounters.values().stream()
            .mapToLong(AtomicLong::get)
            .sum();
        long totalSuccesses = backendSuccessCounters.values().stream()
            .mapToLong(AtomicLong::get)
            .sum();
        long totalErrors = backendErrorCounters.values().stream()
            .mapToLong(AtomicLong::get)
            .sum();
        
        metrics.put("backend_calls_total", totalCalls);
        metrics.put("backend_calls_success", totalSuccesses);
        metrics.put("backend_calls_errors", totalErrors);
        metrics.put("backend_success_rate", totalCalls > 0 ? (double) totalSuccesses / totalCalls * 100 : 0);
        
        // Cache
        long totalCacheAccess = cacheHits.get() + cacheMisses.get();
        metrics.put("cache_hits", cacheHits.get());
        metrics.put("cache_misses", cacheMisses.get());
        metrics.put("cache_hit_rate", totalCacheAccess > 0 ? (double) cacheHits.get() / totalCacheAccess * 100 : 0);
        
        // Retry
        metrics.put("retry_attempts", retryAttempts.get());
        metrics.put("retry_successes", retrySuccesses.get());
        metrics.put("retry_failures", retryFailures.get());
        metrics.put("retry_success_rate", retryAttempts.get() > 0 ? (double) retrySuccesses.get() / retryAttempts.get() * 100 : 0);
        
        // Validation
        long totalValidations = validationSuccesses.get() + validationFailures.get();
        metrics.put("validation_successes", validationSuccesses.get());
        metrics.put("validation_failures", validationFailures.get());
        metrics.put("validation_success_rate", totalValidations > 0 ? (double) validationSuccesses.get() / totalValidations * 100 : 0);
        
        return metrics;
    }
    
    /**
     * Obtient les métriques par service
     */
    public Map<String, Map<String, Object>> getServiceMetrics() {
        Map<String, Map<String, Object>> serviceMetrics = new HashMap<>();
        
        for (String serviceName : backendCallCounters.keySet()) {
            Map<String, Object> metrics = new HashMap<>();
            
            long calls = backendCallCounters.getOrDefault(serviceName, new AtomicLong(0)).get();
            long successes = backendSuccessCounters.getOrDefault(serviceName, new AtomicLong(0)).get();
            long errors = backendErrorCounters.getOrDefault(serviceName, new AtomicLong(0)).get();
            
            metrics.put("calls", calls);
            metrics.put("successes", successes);
            metrics.put("errors", errors);
            metrics.put("success_rate", calls > 0 ? (double) successes / calls * 100 : 0);
            
            ResponseTimeStats stats = responseTimeStats.get(serviceName);
            if (stats != null) {
                metrics.put("response_time_min_ms", stats.getMin());
                metrics.put("response_time_max_ms", stats.getMax());
                metrics.put("response_time_avg_ms", stats.getAverage());
                metrics.put("response_time_p95_ms", stats.getP95());
            }
            
            serviceMetrics.put(serviceName, metrics);
        }
        
        return serviceMetrics;
    }
    
    /**
     * Obtient un résumé des métriques sous forme de String (pour logging)
     */
    public String getMetricsSummary() {
        Map<String, Object> metrics = getGlobalMetrics();
        return String.format(
            "Métriques UnionFlow - Uptime: %dh, Backend: %d appels (%d%% succès), " +
            "Cache: %d hits (%d%% hit rate), Retry: %d tentatives (%d%% succès)",
            metrics.get("uptime_hours"),
            metrics.get("backend_calls_total"),
            Math.round((Double) metrics.get("backend_success_rate")),
            metrics.get("cache_hits"),
            Math.round((Double) metrics.get("cache_hit_rate")),
            metrics.get("retry_attempts"),
            Math.round((Double) metrics.get("retry_success_rate"))
        );
    }
    
    /**
     * Réinitialise toutes les métriques
     */
    public void resetMetrics() {
        LOG.info("Réinitialisation de toutes les métriques");
        
        backendCallCounters.clear();
        backendSuccessCounters.clear();
        backendErrorCounters.clear();
        responseTimeStats.clear();
        
        cacheHits.set(0);
        cacheMisses.set(0);
        retryAttempts.set(0);
        retrySuccesses.set(0);
        retryFailures.set(0);
        validationSuccesses.set(0);
        validationFailures.set(0);
    }
    
    /**
     * Log les métriques actuelles
     */
    public void logMetrics() {
        LOG.info(getMetricsSummary());
        
        // Détail par service
        Map<String, Map<String, Object>> serviceMetrics = getServiceMetrics();
        for (Map.Entry<String, Map<String, Object>> entry : serviceMetrics.entrySet()) {
            Map<String, Object> metrics = entry.getValue();
            LOG.infof("Service %s: %d appels, %d ms avg, %d%% succès",
                entry.getKey(),
                metrics.get("calls"),
                metrics.getOrDefault("response_time_avg_ms", 0L),
                Math.round((Double) metrics.getOrDefault("success_rate", 0.0)));
        }
    }
    
    /**
     * Classe interne pour statistiques de temps de réponse
     */
    private static class ResponseTimeStats {
        private long min = Long.MAX_VALUE;
        private long max = Long.MIN_VALUE;
        private long sum = 0;
        private long count = 0;
        private final List<Long> samples = new ArrayList<>();
        private static final int MAX_SAMPLES = 100; // Garder les 100 derniers pour P95
        
        public synchronized void record(long durationMs) {
            min = Math.min(min, durationMs);
            max = Math.max(max, durationMs);
            sum += durationMs;
            count++;
            
            samples.add(durationMs);
            if (samples.size() > MAX_SAMPLES) {
                samples.remove(0);
            }
        }
        
        public long getMin() {
            return min == Long.MAX_VALUE ? 0 : min;
        }
        
        public long getMax() {
            return max == Long.MIN_VALUE ? 0 : max;
        }
        
        public long getAverage() {
            return count > 0 ? sum / count : 0;
        }
        
        public long getP95() {
            if (samples.isEmpty()) return 0;
            
            List<Long> sorted = new ArrayList<>(samples);
            sorted.sort(Long::compareTo);
            int index = (int) Math.ceil(sorted.size() * 0.95) - 1;
            return sorted.get(Math.max(0, index));
        }
    }
}

