<?php
declare(strict_types=1);

namespace App\Services;

final class SecurityInformationEventManagementService
{
    private const MAX_ROWS = 30000;

    /** @return array<string,mixed> */
    public function report(int $hours = 24): array
    {
        $hours = max(1, min(720, $hours));
        $base = defined('BASE_PATH') ? BASE_PATH : dirname(__DIR__, 2);
        $sources = [
            'Événements sécurité' => $base . '/storage/logs/security/security-events.log',
            'Alertes sécurité' => $base . '/storage/logs/security/security-alerts.log',
        ];
        $rows = [];
        $sourceHealth = [];
        foreach ($sources as $label => $path) {
            $family = $this->readLogFamily($path);
            $rows = array_merge($rows, $family);
            $sourceHealth[] = [
                'label' => $label,
                'available' => is_file($path),
                'events' => count($family),
                'last_event_at' => $this->latestDate($family),
            ];
        }

        $since = time() - ($hours * 3600);
        $rows = array_values(array_filter($rows, fn(array $row): bool => $this->timestamp($row) >= $since));
        usort($rows, fn(array $a, array $b): int => $this->timestamp($a) <=> $this->timestamp($b));

        $severity = ['info'=>0, 'warning'=>0, 'high'=>0, 'critical'=>0];
        $categories = [];
        $timeline = [];
        $normalized = [];
        $actors = [];
        $paths = [];

        foreach ($rows as $row) {
            $event = $this->event($row);
            $level = $this->severity($row, $event);
            $category = $this->category($event);
            $context = is_array($row['context'] ?? null) ? $row['context'] : [];
            $actorRaw = (string)($context['ip_hash'] ?? $context['user_id'] ?? 'inconnu');
            $actor = $this->shortHash($actorRaw);
            $path = $this->safePath((string)($context['path'] ?? '/'));
            $date = $this->date($row);
            $bucket = $hours <= 48 ? date('Y-m-d H:00', $this->timestamp($row)) : date('Y-m-d', $this->timestamp($row));

            $severity[$level]++;
            $categories[$category] = ($categories[$category] ?? 0) + 1;
            $timeline[$bucket] = ($timeline[$bucket] ?? 0) + 1;
            $actors[$actor] = ($actors[$actor] ?? 0) + 1;
            $paths[$path] = ($paths[$path] ?? 0) + 1;
            $normalized[] = [
                'timestamp'=>$date,
                'severity'=>$level,
                'category'=>$category,
                'event'=>$event,
                'actor'=>$actor,
                'path'=>$path,
                'request_id'=>$this->shortHash((string)($context['request_id'] ?? '')),
            ];
        }

        arsort($categories); arsort($actors); arsort($paths); ksort($timeline);
        $detections = $this->detect($rows, $normalized);
        $risk = min(100,
            $severity['critical'] * 22 +
            $severity['high'] * 9 +
            $severity['warning'] * 2 +
            array_sum(array_map(static fn(array $d): int => (int)$d['weight'], $detections))
        );

        return [
            'hours'=>$hours,
            'generated_at'=>date('Y-m-d H:i:s'),
            'events'=>count($rows),
            'risk_score'=>$risk,
            'status'=>$risk >= 75 ? 'Critique' : ($risk >= 40 ? 'Sous surveillance' : 'Stable'),
            'severity'=>$severity,
            'categories'=>array_slice($categories, 0, 10, true),
            'timeline'=>$timeline,
            'top_actors'=>array_slice($actors, 0, 8, true),
            'top_paths'=>array_slice($paths, 0, 8, true),
            'detections'=>$detections,
            'source_health'=>$sourceHealth,
            'recent_events'=>array_slice(array_reverse($normalized), 0, 150),
            'recommendations'=>$this->recommendations($risk, $detections, $sourceHealth),
        ];
    }

    /** @return list<array<string,mixed>> */
    private function detect(array $rawRows, array $events): array
    {
        $rules = [];
        $countByEvent = [];
        $countByActor = [];
        $sensitiveByActor = [];
        foreach ($events as $event) {
            $name = strtolower((string)$event['event']);
            $actor = (string)$event['actor'];
            $countByEvent[$name] = ($countByEvent[$name] ?? 0) + 1;
            $countByActor[$actor] = ($countByActor[$actor] ?? 0) + 1;
            if ($this->isSensitive($name)) $sensitiveByActor[$actor] = ($sensitiveByActor[$actor] ?? 0) + 1;
        }

        foreach ($countByEvent as $event => $count) {
            if ($count >= 15 && preg_match('/login|auth|mfa|denied|refus|failed|echec|rate/', $event)) {
                $rules[] = $this->detection('AUTH-BRUTE-001', 'Rafale d’échecs ou refus d’authentification', 'high', $count, 16, 'Révoquer les sessions concernées et vérifier le compte ou l’appareil.');
            }
            if ($count >= 8 && preg_match('/permission|role|privilege|escalation/', $event)) {
                $rules[] = $this->detection('IAM-ESCALATION-001', 'Activité inhabituelle sur les rôles ou permissions', 'critical', $count, 24, 'Contrôler immédiatement les changements d’autorisation et l’intégrité du journal.');
            }
            if ($count >= 5 && preg_match('/backup|export|download|database|sql/', $event)) {
                $rules[] = $this->detection('DATA-EXFIL-001', 'Volume inhabituel d’exports ou d’accès aux données', 'high', $count, 18, 'Vérifier les exports, l’auteur pseudonymisé et les sessions actives.');
            }
        }
        foreach ($countByActor as $actor => $count) {
            if ($actor !== 'inconnu' && $count >= 100) {
                $rules[] = $this->detection('BEHAVIOR-VOLUME-001', 'Volume d’activité très élevé pour un acteur', 'warning', $count, 8, 'Comparer avec le profil UEBA et examiner les chemins les plus sollicités.', $actor);
            }
        }
        foreach ($sensitiveByActor as $actor => $count) {
            if ($actor !== 'inconnu' && $count >= 6) {
                $rules[] = $this->detection('BEHAVIOR-SENSITIVE-001', 'Séquence rapprochée d’actions sensibles', 'high', $count, 18, 'Ouvrir un incident et confirmer la légitimité des actions.', $actor);
            }
        }

        usort($rules, fn(array $a, array $b): int => [$this->rank((string)$b['severity']), $b['count']] <=> [$this->rank((string)$a['severity']), $a['count']]);
        return array_slice($rules, 0, 50);
    }

    /** @return array<string,mixed> */
    private function detection(string $rule, string $title, string $severity, int $count, int $weight, string $recommendation, string $actor = ''): array
    {
        return compact('rule','title','severity','count','weight','recommendation','actor');
    }

    /** @return list<string> */
    private function recommendations(int $risk, array $detections, array $sources): array
    {
        $items = [];
        if ($risk >= 75) $items[] = 'Activer la procédure de réponse aux incidents et confiner les sessions présentant le plus de risque.';
        elseif ($risk >= 40) $items[] = 'Analyser les détections High et Critical puis comparer avec les profils UEBA.';
        if ($detections !== []) $items[] = 'Documenter chaque détection retenue dans le registre de réponse aux incidents.';
        if (array_filter($sources, static fn(array $s): bool => !$s['available'])) $items[] = 'Une source de journal est indisponible : vérifier les droits du stockage privé et la rotation des fichiers.';
        if ($items === []) $items[] = 'Aucune action urgente. Maintenir la surveillance, le MFA et les sauvegardes chiffrées.';
        return $items;
    }

    /** @return list<array<string,mixed>> */
    private function readLogFamily(string $file): array
    {
        $rows = [];
        $paths = array_filter([$file, $file.'.1', $file.'.2', $file.'.3', $file.'.4'], 'is_file');
        foreach (array_reverse($paths) as $path) {
            $handle = @fopen($path, 'rb');
            if ($handle === false) continue;
            while (($line = fgets($handle)) !== false) {
                $decoded = json_decode(trim($line), true);
                if (is_array($decoded)) $rows[] = $decoded;
                if (count($rows) > self::MAX_ROWS) array_shift($rows);
            }
            fclose($handle);
        }
        return $rows;
    }

    private function latestDate(array $rows): ?string
    {
        if ($rows === []) return null;
        $latest = 0;
        foreach ($rows as $row) $latest = max($latest, $this->timestamp($row));
        return $latest > 0 ? date('Y-m-d H:i:s', $latest) : null;
    }

    private function isSensitive(string $event): bool
    {
        return (bool)preg_match('/permission|role|mfa|session|backup|export|update|secret|vault|sql|impersonation|zero.trust/', $event);
    }

    /** @param array<string,mixed> $row */
    private function event(array $row): string
    {
        return mb_substr((string)($row['context']['event'] ?? $row['message'] ?? $row['event'] ?? 'security.event'), 0, 180);
    }

    /** @param array<string,mixed> $row */
    private function severity(array $row, string $event): string
    {
        $raw = strtolower((string)($row['context']['severity'] ?? $row['severity'] ?? $row['level'] ?? 'warning'));
        if (preg_match('/critical|tamper|integrity.failed|escalation/', strtolower($event))) return 'critical';
        if (preg_match('/anomaly|denied|failed|revoked|lockout/', strtolower($event))) return 'high';
        return match ($raw) {
            'critical','fatal' => 'critical',
            'error','high' => 'high',
            'info','notice' => 'info',
            default => 'warning',
        };
    }

    private function category(string $event): string
    {
        $event = strtolower($event);
        return match (true) {
            (bool)preg_match('/login|auth|mfa|session/', $event) => 'Identité & accès',
            (bool)preg_match('/permission|role|privilege|impersonation/', $event) => 'Autorisations',
            (bool)preg_match('/backup|restore|export|download/', $event) => 'Protection des données',
            (bool)preg_match('/update|package|signature|integrity/', $event) => 'Intégrité & mises à jour',
            (bool)preg_match('/sql|database/', $event) => 'Base de données',
            (bool)preg_match('/upload|file|path/', $event) => 'Fichiers',
            (bool)preg_match('/api|connector|webhook/', $event) => 'API & connecteurs',
            (bool)preg_match('/rate|scan|anomaly|attack/', $event) => 'Détection offensive',
            default => 'Événements applicatifs',
        };
    }

    /** @param array<string,mixed> $row */
    private function timestamp(array $row): int
    {
        $ts = strtotime((string)($row['timestamp'] ?? $row['context']['timestamp'] ?? ''));
        return $ts === false ? time() : $ts;
    }

    /** @param array<string,mixed> $row */
    private function date(array $row): string { return date('Y-m-d H:i:s', $this->timestamp($row)); }
    private function safePath(string $path): string { return mb_substr(parse_url($path, PHP_URL_PATH) ?: '/', 0, 240); }
    private function shortHash(string $value): string { return $value === '' || $value === 'inconnu' ? ($value ?: 'inconnu') : substr($value, 0, 12).'…'; }
    private function rank(string $severity): int { return ['info'=>1,'warning'=>2,'high'=>3,'critical'=>4][$severity] ?? 2; }
}
