<?php

declare(strict_types=1);

namespace App\Services\Security;

require_once __DIR__ . '/AlertService.php';

use PDO;
use Database;

/**
 * Service de monitoring des performances
 * Mesure et alerte sur les temps de réponse, utilisation mémoire, etc.
 */
class PerformanceMonitor {
    
    private static ?PDO $db = null;
    private static ?float $requestStartTime = null;
    private static ?float $requestStartMemory = null;
    private static array $dbQueries = [];
    private static float $dbTotalTime = 0;
    private static int $dbQueryCount = 0;
    
    // Seuils par défaut (en millisecondes)
    const DEFAULT_THRESHOLDS = [
        'response_time_warning' => 1000,    // 1 seconde
        'response_time_critical' => 3000,   // 3 secondes
        'db_time_warning' => 500,           // 500ms
        'db_time_critical' => 1000,         // 1 seconde
        'memory_warning' => 64,             // 64 MB
        'memory_critical' => 128,           // 128 MB
        'db_queries_warning' => 20,         // 20 requêtes
        'db_queries_critical' => 50         // 50 requêtes
    ];
    
    // Seuils spécifiques par endpoint
    const ENDPOINT_THRESHOLDS = [
        '/api/operations/export' => [
            'response_time_warning' => 5000,
            'response_time_critical' => 10000,
            'memory_warning' => 256,
            'memory_critical' => 512
        ],
        '/api/chat/rooms' => [
            'response_time_warning' => 500,
            'response_time_critical' => 1000
        ],
        '/api/logs' => [
            'response_time_warning' => 200,
            'response_time_critical' => 500
        ]
    ];
    
    /**
     * Démarrer le monitoring d'une requête
     */
    public static function startRequest(): void {
        self::$requestStartTime = microtime(true);
        self::$requestStartMemory = memory_get_usage(true);
        self::$dbQueries = [];
        self::$dbTotalTime = 0;
        self::$dbQueryCount = 0;
    }
    
    /**
     * Démarrer le monitoring d'une requête DB
     */
    public static function startDbQuery(string $query): void {
        self::$dbQueries[] = [
            'query' => $query,
            'start' => microtime(true),
            'duration' => 0
        ];
    }
    
    /**
     * Terminer le monitoring d'une requête DB
     */
    public static function endDbQuery(): void {
        if (!empty(self::$dbQueries)) {
            $lastIndex = count(self::$dbQueries) - 1;
            $duration = (microtime(true) - self::$dbQueries[$lastIndex]['start']) * 1000;
            self::$dbQueries[$lastIndex]['duration'] = $duration;
            self::$dbTotalTime += $duration;
            self::$dbQueryCount++;
            
            // Alerter si requête très lente
            if ($duration > 1000) { // Plus d'1 seconde
                AlertService::trigger('SLOW_QUERY', [
                    'query' => substr(self::$dbQueries[$lastIndex]['query'], 0, 500),
                    'duration_ms' => $duration,
                    'message' => "Requête SQL lente détectée : {$duration}ms"
                ], 'WARNING');
            }
        }
    }
    
    /**
     * Terminer le monitoring d'une requête HTTP
     */
    public static function endRequest(string $endpoint, string $method, int $httpStatus): void {
        if (self::$requestStartTime === null) {
            return; // Monitoring non démarré
        }
        
        // Calculer les métriques
        $responseTime = (microtime(true) - self::$requestStartTime) * 1000;
        $memoryPeak = memory_get_peak_usage(true) / 1024 / 1024; // En MB
        $memoryStart = self::$requestStartMemory / 1024 / 1024;
        $memoryUsed = $memoryPeak - $memoryStart;
        
        // Enrichir avec les infos de requête
        $ip = \AppConfig::getInstance()->getClientIp();
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? null;
        $requestSize = strlen(file_get_contents('php://input'));
        
        // ID utilisateur si connecté
        $userId = null;
        if (session_status() === PHP_SESSION_ACTIVE && isset($_SESSION['user_id'])) {
            $userId = $_SESSION['user_id'];
        }
        
        // Enregistrer les métriques
        self::saveMetrics([
            'endpoint' => $endpoint,
            'method' => $method,
            'response_time_ms' => (int)$responseTime,
            'db_time_ms' => (int)self::$dbTotalTime,
            'db_queries_count' => self::$dbQueryCount,
            'memory_peak_mb' => $memoryPeak,
            'memory_start_mb' => $memoryStart,
            'http_status' => $httpStatus,
            'user_id' => $userId,
            'ip_address' => $ip,
            'user_agent' => $userAgent,
            'request_size' => $requestSize,
            'response_size' => ob_get_length() ?: 0
        ]);
        
        // Vérifier les seuils et alerter si nécessaire
        self::checkThresholds($endpoint, $responseTime, self::$dbTotalTime, $memoryPeak);
        
        // Vérifier la dégradation des performances
        self::checkPerformanceDegradation($endpoint, $responseTime);
        
        // Réinitialiser pour la prochaine requête
        self::reset();
    }
    
    /**
     * Sauvegarder les métriques en base
     */
    private static function saveMetrics(array $metrics): void {
        try {
            $db = self::getDb();
            
            $stmt = $db->prepare('
                INSERT INTO sec_performance_metrics (
                    endpoint, method, response_time_ms, db_time_ms, db_queries_count,
                    memory_peak_mb, memory_start_mb, http_status, user_id, 
                    ip_address, user_agent, request_size, response_size
                ) VALUES (
                    :endpoint, :method, :response_time, :db_time, :db_queries,
                    :memory_peak, :memory_start, :status, :user_id,
                    :ip, :agent, :req_size, :resp_size
                )
            ');
            
            $stmt->execute([
                'endpoint' => $metrics['endpoint'],
                'method' => $metrics['method'],
                'response_time' => $metrics['response_time_ms'],
                'db_time' => $metrics['db_time_ms'],
                'db_queries' => $metrics['db_queries_count'],
                'memory_peak' => $metrics['memory_peak_mb'],
                'memory_start' => $metrics['memory_start_mb'],
                'status' => $metrics['http_status'],
                'user_id' => $metrics['user_id'],
                'ip' => $metrics['ip_address'],
                'agent' => $metrics['user_agent'],
                'req_size' => $metrics['request_size'],
                'resp_size' => $metrics['response_size']
            ]);
            
        } catch (\Exception $e) {
            error_log("Failed to save performance metrics: " . $e->getMessage());
        }
    }
    
    /**
     * Vérifier les seuils de performance
     */
    private static function checkThresholds(
        string $endpoint, 
        float $responseTime, 
        float $dbTime, 
        float $memoryPeak
    ): void {
        // Obtenir les seuils pour cet endpoint
        $thresholds = self::getThresholdsForEndpoint($endpoint);
        
        // Vérifier temps de réponse
        if ($responseTime > $thresholds['response_time_critical']) {
            AlertService::trigger('PERFORMANCE_CRITICAL', [
                'endpoint' => $endpoint,
                'response_time_ms' => $responseTime,
                'threshold_ms' => $thresholds['response_time_critical'],
                'db_time_ms' => $dbTime,
                'message' => "Performance critique sur $endpoint : {$responseTime}ms"
            ], 'ERROR');
        } elseif ($responseTime > $thresholds['response_time_warning']) {
            AlertService::trigger('PERFORMANCE_SLOW', [
                'endpoint' => $endpoint,
                'response_time_ms' => $responseTime,
                'threshold_ms' => $thresholds['response_time_warning'],
                'db_time_ms' => $dbTime,
                'message' => "Performance lente sur $endpoint : {$responseTime}ms"
            ], 'WARNING');
        }
        
        // Vérifier temps DB
        if ($dbTime > $thresholds['db_time_critical']) {
            AlertService::trigger('DB_PERFORMANCE_CRITICAL', [
                'endpoint' => $endpoint,
                'db_time_ms' => $dbTime,
                'threshold_ms' => $thresholds['db_time_critical'],
                'queries_count' => self::$dbQueryCount,
                'message' => "Temps DB critique : {$dbTime}ms pour " . self::$dbQueryCount . " requêtes"
            ], 'ERROR');
        } elseif ($dbTime > $thresholds['db_time_warning']) {
            AlertService::trigger('DB_PERFORMANCE_SLOW', [
                'endpoint' => $endpoint,
                'db_time_ms' => $dbTime,
                'threshold_ms' => $thresholds['db_time_warning'],
                'queries_count' => self::$dbQueryCount,
                'message' => "Temps DB lent : {$dbTime}ms"
            ], 'WARNING');
        }
        
        // Vérifier mémoire
        if ($memoryPeak > $thresholds['memory_critical']) {
            AlertService::trigger('MEMORY_CRITICAL', [
                'endpoint' => $endpoint,
                'memory_mb' => $memoryPeak,
                'threshold_mb' => $thresholds['memory_critical'],
                'message' => "Utilisation mémoire critique : {$memoryPeak}MB"
            ], 'ERROR');
        } elseif ($memoryPeak > $thresholds['memory_warning']) {
            AlertService::trigger('MEMORY_HIGH', [
                'endpoint' => $endpoint,
                'memory_mb' => $memoryPeak,
                'threshold_mb' => $thresholds['memory_warning'],
                'message' => "Utilisation mémoire élevée : {$memoryPeak}MB"
            ], 'WARNING');
        }
        
        // Vérifier nombre de requêtes DB
        if (self::$dbQueryCount > $thresholds['db_queries_critical']) {
            AlertService::trigger('DB_QUERIES_EXCESSIVE', [
                'endpoint' => $endpoint,
                'queries_count' => self::$dbQueryCount,
                'threshold' => $thresholds['db_queries_critical'],
                'message' => "Trop de requêtes DB : " . self::$dbQueryCount
            ], 'ERROR');
        }
    }
    
    /**
     * Vérifier la dégradation des performances
     */
    private static function checkPerformanceDegradation(string $endpoint, float $currentTime): void {
        try {
            $db = self::getDb();
            
            // Calculer la moyenne sur les 100 dernières requêtes
            $stmt = $db->prepare('
                SELECT AVG(response_time_ms) as avg_time,
                       STDDEV(response_time_ms) as stddev_time,
                       COUNT(*) as sample_size
                FROM (
                    SELECT response_time_ms
                    FROM sec_performance_metrics
                    WHERE endpoint = :endpoint
                      AND http_status < 500
                      AND created_at >= DATE_SUB(NOW(), INTERVAL 1 HOUR)
                    ORDER BY created_at DESC
                    LIMIT 100
                ) as recent
            ');
            
            $stmt->execute(['endpoint' => $endpoint]);
            $stats = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if ($stats && $stats['sample_size'] >= 10) {
                $avgTime = (float)$stats['avg_time'];
                $stdDev = (float)$stats['stddev_time'];
                
                // Alerter si 2x plus lent que la moyenne
                if ($currentTime > ($avgTime * 2) && $avgTime > 100) {
                    AlertService::trigger('PERFORMANCE_DEGRADATION', [
                        'endpoint' => $endpoint,
                        'current_time_ms' => $currentTime,
                        'average_time_ms' => $avgTime,
                        'stddev_ms' => $stdDev,
                        'factor' => round($currentTime / $avgTime, 2),
                        'sample_size' => $stats['sample_size'],
                        'message' => "Performance dégradée : " . round($currentTime / $avgTime, 1) . "x plus lent que la moyenne"
                    ], 'WARNING');
                }
            }
            
        } catch (\Exception $e) {
            error_log("Failed to check performance degradation: " . $e->getMessage());
        }
    }
    
    /**
     * Obtenir les seuils pour un endpoint
     */
    private static function getThresholdsForEndpoint(string $endpoint): array {
        // Chercher des seuils spécifiques
        foreach (self::ENDPOINT_THRESHOLDS as $pattern => $thresholds) {
            if (strpos($endpoint, $pattern) === 0) {
                return array_merge(self::DEFAULT_THRESHOLDS, $thresholds);
            }
        }
        
        // Retourner les seuils par défaut
        return self::DEFAULT_THRESHOLDS;
    }
    
    /**
     * Obtenir les statistiques de performance
     */
    public static function getStats(string $endpoint = null, int $hours = 24): array {
        $db = self::getDb();
        
        $whereClause = 'WHERE created_at >= DATE_SUB(NOW(), INTERVAL :hours HOUR)';
        $params = ['hours' => $hours];
        
        if ($endpoint) {
            $whereClause .= ' AND endpoint = :endpoint';
            $params['endpoint'] = $endpoint;
        }
        
        // Statistiques globales
        $globalStmt = $db->prepare("
            SELECT 
                COUNT(*) as total_requests,
                AVG(response_time_ms) as avg_response_time,
                MIN(response_time_ms) as min_response_time,
                MAX(response_time_ms) as max_response_time,
                AVG(db_time_ms) as avg_db_time,
                AVG(memory_peak_mb) as avg_memory,
                SUM(CASE WHEN http_status >= 500 THEN 1 ELSE 0 END) as errors_5xx,
                SUM(CASE WHEN http_status >= 400 AND http_status < 500 THEN 1 ELSE 0 END) as errors_4xx
            FROM sec_performance_metrics
            $whereClause
        ");
        
        $globalStmt->execute($params);
        $globalStats = $globalStmt->fetch(PDO::FETCH_ASSOC);
        
        // Top endpoints lents
        $slowStmt = $db->prepare("
            SELECT 
                endpoint,
                method,
                COUNT(*) as requests,
                AVG(response_time_ms) as avg_time,
                MAX(response_time_ms) as max_time
            FROM sec_performance_metrics
            $whereClause
            GROUP BY endpoint, method
            ORDER BY avg_time DESC
            LIMIT 10
        ");
        
        $slowStmt->execute($params);
        $slowEndpoints = $slowStmt->fetchAll(PDO::FETCH_ASSOC);
        
        // Requêtes DB lentes
        $slowDbStmt = $db->prepare("
            SELECT 
                endpoint,
                MAX(db_time_ms) as max_db_time,
                AVG(db_time_ms) as avg_db_time,
                AVG(db_queries_count) as avg_queries
            FROM sec_performance_metrics
            WHERE db_time_ms > 500
              AND created_at >= DATE_SUB(NOW(), INTERVAL :hours HOUR)
            GROUP BY endpoint
            ORDER BY max_db_time DESC
            LIMIT 10
        ");
        
        $slowDbStmt->execute(['hours' => $hours]);
        $slowDbQueries = $slowDbStmt->fetchAll(PDO::FETCH_ASSOC);
        
        return [
            'timeframe_hours' => $hours,
            'global' => $globalStats,
            'slow_endpoints' => $slowEndpoints,
            'slow_db_operations' => $slowDbQueries,
            'current_memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
            'current_load' => sys_getloadavg()
        ];
    }
    
    /**
     * Nettoyer les anciennes métriques
     */
    public static function cleanup(int $daysToKeep = 30): int {
        try {
            $db = self::getDb();
            
            $stmt = $db->prepare('
                DELETE FROM sec_performance_metrics 
                WHERE created_at < DATE_SUB(NOW(), INTERVAL :days DAY)
            ');
            
            $stmt->execute(['days' => $daysToKeep]);
            
            return $stmt->rowCount();
        } catch (\Exception $e) {
            error_log("Failed to cleanup performance metrics: " . $e->getMessage());
            return 0;
        }
    }
    
    /**
     * Réinitialiser le monitoring
     */
    private static function reset(): void {
        self::$requestStartTime = null;
        self::$requestStartMemory = null;
        self::$dbQueries = [];
        self::$dbTotalTime = 0;
        self::$dbQueryCount = 0;
    }
    
    /**
     * Obtenir l'instance de base de données
     */
    private static function getDb(): PDO {
        if (self::$db === null) {
            self::$db = Database::getInstance();
        }
        return self::$db;
    }
}