<?php

declare(strict_types=1);

namespace App\Services\Security;

require_once __DIR__ . '/../../Services/ApiService.php';
require_once __DIR__ . '/EmailThrottler.php';

use PDO;
use Database;
use App\Services\ApiService;
use AppConfig;
use App\Services\LogService;

/**
 * Service central de gestion des alertes de sécurité et monitoring
 */
class AlertService {
    
    // Niveaux d'alerte avec leur configuration
    const ALERT_LEVELS = [
        'INFO'     => ['email' => false, 'log' => true, 'throttle' => 0],
        'WARNING'  => ['email' => true, 'log' => true, 'throttle' => 3600],    // 1h
        'ERROR'    => ['email' => true, 'log' => true, 'throttle' => 900],     // 15min
        'CRITICAL' => ['email' => true, 'log' => true, 'throttle' => 300],     // 5min
        'SECURITY' => ['email' => true, 'log' => true, 'throttle' => 0, 'priority' => true]
    ];
    
    // Types d'alertes avec leur niveau par défaut
    const ALERT_TYPES = [
        'BRUTE_FORCE'           => 'SECURITY',
        'UNAUTHORIZED_ACCESS'   => 'CRITICAL',
        'SQL_INJECTION'         => 'SECURITY',
        'SQL_ERROR'             => 'ERROR',
        'DB_CONNECTION'         => 'CRITICAL',
        'DB_DEADLOCK'           => 'ERROR',
        'PERFORMANCE_SLOW'      => 'WARNING',
        'PERFORMANCE_CRITICAL'  => 'ERROR',
        'HTTP_500'              => 'ERROR',
        'HTTP_404_PATTERN'      => 'WARNING',
        'SUSPICIOUS_PATTERN'    => 'WARNING',
        'MEMORY_HIGH'           => 'WARNING',
        'DISK_SPACE'            => 'CRITICAL'
    ];
    
    private static ?PDO $db = null;
    private static ?EmailThrottler $throttler = null;
    
    /**
     * Déclencher une alerte
     */
    public static function trigger(string $type, array $data = [], ?string $level = null): void {
        try {
            // Déterminer le niveau si non fourni
            if ($level === null) {
                $level = self::ALERT_TYPES[$type] ?? 'WARNING';
            }
            
            // Enrichir le contexte
            $context = self::enrichContext($type, $level, $data);
            
            // Enregistrer en base de données
            $alertId = self::saveAlert($type, $level, $context);
            
            // Logger
            self::logAlert($type, $level, $context);
            
            // Envoyer email si nécessaire et non throttled
            if (self::shouldSendEmail($type, $level)) {
                self::sendAlertEmail($type, $level, $context, $alertId);
            }
            
            // Actions automatiques selon le type
            self::executeAutomaticActions($type, $context);
            
        } catch (\Exception $e) {
            error_log("AlertService Error: " . $e->getMessage());
        }
    }
    
    /**
     * Enrichir le contexte avec des informations système
     */
    private static function enrichContext(string $type, string $level, array $data): array {
        $context = $data;
        
        // Ajouter les informations de base
        $context['timestamp'] = date('Y-m-d H:i:s');
        $context['environment'] = AppConfig::getInstance()->getEnvironment();
        $context['server'] = $_SERVER['SERVER_NAME'] ?? 'unknown';
        
        // Ajouter les informations de requête
        if (isset($_SERVER['REQUEST_URI'])) {
            $context['request'] = [
                'uri' => $_SERVER['REQUEST_URI'],
                'method' => $_SERVER['REQUEST_METHOD'] ?? 'unknown',
                'ip' => \AppConfig::getInstance()->getClientIp(),
                'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
            ];
        }
        
        // Ajouter les informations de session
        if (session_status() === PHP_SESSION_ACTIVE) {
            $context['session'] = [
                'user_id' => $_SESSION['user_id'] ?? null,
                'entity_id' => $_SESSION['entity_id'] ?? null,
                'session_id' => substr(session_id(), 0, 8) . '...' // Tronquer pour sécurité
            ];
        }
        
        // Ajouter les métriques système
        $context['system'] = [
            'memory_usage_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
            'memory_peak_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
            'load_average' => sys_getloadavg()
        ];
        
        return $context;
    }
    
    /**
     * Sauvegarder l'alerte en base de données
     */
    private static function saveAlert(string $type, string $level, array $context): int {
        $db = self::getDb();
        
        // Vérifier si une alerte similaire existe déjà (dans les 5 dernières minutes)
        $checkStmt = $db->prepare('
            SELECT id, occurrences 
            FROM sec_alerts 
            WHERE alert_type = :type 
              AND alert_level = :level 
              AND ip_address = :ip
              AND endpoint = :endpoint
              AND resolved = 0
              AND last_seen >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)
            LIMIT 1
        ');
        
        $ip = $context['request']['ip'] ?? null;
        $endpoint = $context['request']['uri'] ?? null;
        
        $checkStmt->execute([
            'type' => $type,
            'level' => $level,
            'ip' => $ip,
            'endpoint' => $endpoint
        ]);
        
        $existing = $checkStmt->fetch(PDO::FETCH_ASSOC);
        
        if ($existing) {
            // Mettre à jour l'alerte existante
            $updateStmt = $db->prepare('
                UPDATE sec_alerts 
                SET occurrences = occurrences + 1,
                    last_seen = NOW(),
                    details = :details
                WHERE id = :id
            ');
            
            $updateStmt->execute([
                'id' => $existing['id'],
                'details' => json_encode($context)
            ]);
            
            return (int)$existing['id'];
        } else {
            // Créer une nouvelle alerte
            $insertStmt = $db->prepare('
                INSERT INTO sec_alerts (
                    alert_type, alert_level, ip_address, user_id, username,
                    endpoint, method, details, first_seen, last_seen
                ) VALUES (
                    :type, :level, :ip, :user_id, :username,
                    :endpoint, :method, :details, NOW(), NOW()
                )
            ');
            
            $insertStmt->execute([
                'type' => $type,
                'level' => $level,
                'ip' => $ip,
                'user_id' => $context['session']['user_id'] ?? null,
                'username' => $context['username'] ?? null,
                'endpoint' => $endpoint,
                'method' => $context['request']['method'] ?? null,
                'details' => json_encode($context)
            ]);
            
            return (int)$db->lastInsertId();
        }
    }
    
    /**
     * Logger l'alerte
     */
    private static function logAlert(string $type, string $level, array $context): void {
        $message = sprintf(
            "[ALERT] %s - %s: %s",
            $level,
            $type,
            $context['message'] ?? 'No message provided'
        );
        
        // Utiliser LogService existant
        if (class_exists('LogService')) {
            \LogService::log($message, [
                'level' => strtolower($level),
                'alert_type' => $type,
                'context' => $context
            ]);
        } else {
            error_log($message);
        }
    }
    
    /**
     * Vérifier si un email doit être envoyé
     */
    private static function shouldSendEmail(string $type, string $level): bool {
        $config = self::ALERT_LEVELS[$level] ?? [];
        
        if (!($config['email'] ?? false)) {
            return false;
        }
        
        // Vérifier l'environnement
        $env = AppConfig::getInstance()->getEnvironment();
        if ($env === 'development' && $level !== 'SECURITY') {
            return false; // Pas d'emails en dev sauf sécurité
        }
        
        // Vérifier le throttling
        $throttler = self::getThrottler();
        $throttleTime = $config['throttle'] ?? 0;
        
        if ($throttleTime > 0 && !$throttler->canSend($type, $throttleTime)) {
            return false;
        }
        
        return true;
    }
    
    /**
     * Envoyer l'email d'alerte
     */
    private static function sendAlertEmail(string $type, string $level, array $context, int $alertId): void {
        try {
            $throttler = self::getThrottler();
            
            // Préparer le contenu de l'email
            $subject = sprintf(
                "[%s] GeoSector %s - %s",
                $level,
                strtoupper($context['environment'] ?? 'PROD'),
                str_replace('_', ' ', $type)
            );
            
            $body = self::formatEmailBody($type, $level, $context, $alertId);
            
            // Destinataire principal
            $to = 'support@unikoffice.com';
            
            // CC pour les alertes critiques
            $cc = [];
            if (in_array($level, ['CRITICAL', 'SECURITY'])) {
                $cc[] = 'admin@geosector.fr';
            }
            
            // Envoyer l'email
            $emailSent = ApiService::sendEmail(
                $to,
                'GeoSector Security',
                'security_alert',
                [
                    'subject' => $subject,
                    'body' => $body,
                    'type' => $type,
                    'level' => $level,
                    'context' => $context
                ]
            );
            
            if ($emailSent) {
                // Marquer l'alerte comme email envoyé
                $db = self::getDb();
                $updateStmt = $db->prepare('
                    UPDATE sec_alerts 
                    SET email_sent = 1, email_sent_at = NOW() 
                    WHERE id = :id
                ');
                $updateStmt->execute(['id' => $alertId]);
                
                // Enregistrer l'envoi pour throttling
                $throttler->recordSent($type);
            }
            
        } catch (\Exception $e) {
            error_log("Failed to send alert email: " . $e->getMessage());
        }
    }
    
    /**
     * Formater le corps de l'email
     */
    private static function formatEmailBody(string $type, string $level, array $context, int $alertId): string {
        $env = strtoupper($context['environment'] ?? 'PRODUCTION');
        $timestamp = $context['timestamp'] ?? date('Y-m-d H:i:s');
        
        $body = "
========================================
ALERTE DE SÉCURITÉ - ACTION REQUISE
========================================

Environnement: $env
Date/Heure: $timestamp
Type: $type
Niveau: $level
ID Alerte: #$alertId

RÉSUMÉ
------
" . ($context['message'] ?? 'Alerte détectée sans message spécifique') . "

DÉTAILS TECHNIQUES
------------------
";
        
        if (isset($context['request'])) {
            $body .= "
Requête:
- Endpoint: " . $context['request']['uri'] . "
- Méthode: " . $context['request']['method'] . "
- IP: " . $context['request']['ip'] . "
- User Agent: " . substr($context['request']['user_agent'] ?? 'N/A', 0, 100) . "
";
        }
        
        if (isset($context['session']) && $context['session']['user_id']) {
            $body .= "
Session:
- User ID: " . $context['session']['user_id'] . "
- Entity ID: " . ($context['session']['entity_id'] ?? 'N/A') . "
";
        }
        
        if (isset($context['system'])) {
            $body .= "
Système:
- Mémoire: " . $context['system']['memory_usage_mb'] . " MB / " . $context['system']['memory_peak_mb'] . " MB (peak)
- Load Average: " . implode(', ', $context['system']['load_average'] ?? []) . "
";
        }
        
        // Ajouter les données spécifiques selon le type
        $body .= self::getTypeSpecificDetails($type, $context);
        
        // Actions recommandées
        $body .= "

ACTIONS RECOMMANDÉES
--------------------
" . self::getRecommendedActions($type, $level, $context) . "

LIENS UTILES
------------
- Logs: https://app.geo.dev/admin/logs
- Dashboard: https://app.geo.dev/admin/security
- Bloquer IP: https://app.geo.dev/admin/block-ip/" . ($context['request']['ip'] ?? '') . "

--
Email automatique généré par GeoSector Security
Ne pas répondre à cet email
";
        
        return $body;
    }
    
    /**
     * Détails spécifiques selon le type d'alerte
     */
    private static function getTypeSpecificDetails(string $type, array $context): string {
        $details = "\nDÉTAILS SPÉCIFIQUES\n-------------------\n";
        
        switch ($type) {
            case 'BRUTE_FORCE':
                $details .= "- Tentatives: " . ($context['attempts'] ?? 'N/A') . "\n";
                $details .= "- Username ciblé: " . ($context['username'] ?? 'N/A') . "\n";
                $details .= "- Période: " . ($context['timeframe'] ?? 'N/A') . "\n";
                break;
                
            case 'SQL_ERROR':
                $details .= "- Erreur SQL: " . ($context['sql_error'] ?? 'N/A') . "\n";
                $details .= "- Code: " . ($context['sql_code'] ?? 'N/A') . "\n";
                break;
                
            case 'PERFORMANCE_SLOW':
            case 'PERFORMANCE_CRITICAL':
                $details .= "- Temps réponse: " . ($context['response_time_ms'] ?? 'N/A') . " ms\n";
                $details .= "- Temps DB: " . ($context['db_time_ms'] ?? 'N/A') . " ms\n";
                $details .= "- Seuil dépassé: " . ($context['threshold_ms'] ?? 'N/A') . " ms\n";
                break;
                
            case 'HTTP_500':
                $details .= "- Message d'erreur: " . ($context['error_message'] ?? 'N/A') . "\n";
                $details .= "- Stack trace: " . substr($context['stack_trace'] ?? 'N/A', 0, 500) . "\n";
                break;
        }
        
        return $details;
    }
    
    /**
     * Actions recommandées selon le type
     */
    private static function getRecommendedActions(string $type, string $level, array $context): string {
        $actions = [];
        
        switch ($type) {
            case 'BRUTE_FORCE':
                $actions[] = "1. Vérifier les logs de connexion";
                $actions[] = "2. Bloquer l'IP si nécessaire";
                $actions[] = "3. Vérifier l'intégrité du compte ciblé";
                $actions[] = "4. Considérer l'activation de 2FA";
                break;
                
            case 'SQL_INJECTION':
                $actions[] = "1. BLOQUER L'IP IMMÉDIATEMENT";
                $actions[] = "2. Vérifier l'intégrité de la base de données";
                $actions[] = "3. Analyser les logs pour d'autres tentatives";
                $actions[] = "4. Patcher la vulnérabilité identifiée";
                break;
                
            case 'DB_CONNECTION':
                $actions[] = "1. Vérifier le statut du serveur MySQL";
                $actions[] = "2. Vérifier les connexions actives";
                $actions[] = "3. Redémarrer le service si nécessaire";
                $actions[] = "4. Vérifier l'espace disque";
                break;
                
            case 'PERFORMANCE_CRITICAL':
                $actions[] = "1. Identifier les requêtes lentes";
                $actions[] = "2. Vérifier la charge serveur";
                $actions[] = "3. Optimiser les requêtes problématiques";
                $actions[] = "4. Considérer un scale-up si récurrent";
                break;
                
            default:
                $actions[] = "1. Examiner les logs détaillés";
                $actions[] = "2. Évaluer l'impact sur les utilisateurs";
                $actions[] = "3. Prendre action selon la criticité";
        }
        
        return implode("\n", $actions);
    }
    
    /**
     * Exécuter des actions automatiques
     */
    private static function executeAutomaticActions(string $type, array $context): void {
        switch ($type) {
            case 'BRUTE_FORCE':
                // Bloquer automatiquement l'IP
                if (isset($context['request']['ip'])) {
                    require_once __DIR__ . '/IPBlocker.php';
                    IPBlocker::block(
                        $context['request']['ip'],
                        3600, // 1 heure
                        'Brute force detected: ' . ($context['attempts'] ?? 0) . ' attempts'
                    );
                }
                break;
                
            case 'SQL_INJECTION':
                // Bloquer immédiatement et définitivement
                if (isset($context['request']['ip'])) {
                    require_once __DIR__ . '/IPBlocker.php';
                    IPBlocker::blockPermanent(
                        $context['request']['ip'],
                        'SQL Injection attempt detected'
                    );
                }
                break;
        }
    }
    
    /**
     * Obtenir l'instance de base de données
     */
    private static function getDb(): PDO {
        if (self::$db === null) {
            self::$db = Database::getInstance();
        }
        return self::$db;
    }
    
    /**
     * Obtenir l'instance du throttler
     */
    private static function getThrottler(): EmailThrottler {
        if (self::$throttler === null) {
            self::$throttler = new EmailThrottler();
        }
        return self::$throttler;
    }
    
    /**
     * Résoudre une alerte
     */
    public static function resolve(int $alertId, int $userId, string $notes = ''): bool {
        try {
            $db = self::getDb();
            $stmt = $db->prepare('
                UPDATE sec_alerts 
                SET resolved = 1,
                    resolved_at = NOW(),
                    resolved_by = :user_id,
                    notes = :notes
                WHERE id = :id
            ');
            
            return $stmt->execute([
                'id' => $alertId,
                'user_id' => $userId,
                'notes' => $notes
            ]);
        } catch (\Exception $e) {
            error_log("Failed to resolve alert: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Obtenir les alertes actives
     */
    public static function getActiveAlerts(int $limit = 50): array {
        try {
            $db = self::getDb();
            $stmt = $db->prepare('
                SELECT * FROM sec_active_alerts 
                LIMIT :limit
            ');
            $stmt->bindValue('limit', $limit, PDO::PARAM_INT);
            $stmt->execute();
            
            return $stmt->fetchAll(PDO::FETCH_ASSOC);
        } catch (\Exception $e) {
            error_log("Failed to get active alerts: " . $e->getMessage());
            return [];
        }
    }
}