<?php
declare(strict_types=1);
namespace App\Services;

use RuntimeException;

final class SecurityIncidentResponseService
{
    private string $stateFile;

    public function __construct()
    {
        $base = defined('BASE_PATH') ? BASE_PATH : dirname(__DIR__, 2);
        $this->stateFile = $base . '/storage/private/security_incident_response.json';
    }

    /** @return array<string,mixed> */
    public function dashboard(int $hours = 24): array
    {
        $report = (new SecurityOperationsCenterService())->report($hours);
        $state = $this->readState();
        $items = [];
        foreach ($report['incidents'] as $incident) {
            $id = $this->fingerprint($incident);
            $saved = is_array($state[$id] ?? null) ? $state[$id] : [];
            $items[] = $incident + [
                'id' => $id,
                'status' => (string)($saved['status'] ?? 'open'),
                'note' => (string)($saved['note'] ?? ''),
                'updated_at' => (string)($saved['updated_at'] ?? ''),
                'updated_by' => (int)($saved['updated_by'] ?? 0),
            ];
        }
        $counts = ['open'=>0, 'acknowledged'=>0, 'resolved'=>0];
        foreach ($items as $item) {
            $status = (string)$item['status'];
            if (isset($counts[$status])) $counts[$status]++;
        }
        return $report + ['response_items'=>$items, 'response_counts'=>$counts];
    }

    public function update(string $id, string $status, string $note, int $userId): void
    {
        if (!preg_match('/^[a-f0-9]{64}$/', $id)) throw new RuntimeException('Identifiant d’incident invalide.');
        if (!in_array($status, ['open','acknowledged','resolved'], true)) throw new RuntimeException('Statut d’incident invalide.');
        $note = trim($note);
        if (mb_strlen($note) > 1000) throw new RuntimeException('La note est limitée à 1 000 caractères.');
        $state = $this->readState();
        $state[$id] = [
            'status'=>$status,
            'note'=>$note,
            'updated_at'=>date('Y-m-d H:i:s'),
            'updated_by'=>$userId,
        ];
        $this->writeState($state);
        SecurityEventMonitor::record('security.incident_response.updated', 'info', [
            'incident_hash'=>$id,
            'status'=>$status,
            'user_id'=>$userId,
        ]);
    }

    /** @param array<string,mixed> $incident */
    private function fingerprint(array $incident): string
    {
        return hash('sha256', implode('|', [
            (string)($incident['event'] ?? ''),
            (string)($incident['actor'] ?? ''),
            (string)($incident['path'] ?? ''),
        ]));
    }

    /** @return array<string,array<string,mixed>> */
    private function readState(): array
    {
        if (!is_file($this->stateFile)) return [];
        $raw = @file_get_contents($this->stateFile);
        if (!is_string($raw) || $raw === '') return [];
        $decoded = json_decode($raw, true);
        return is_array($decoded) ? $decoded : [];
    }

    /** @param array<string,array<string,mixed>> $state */
    private function writeState(array $state): void
    {
        $dir = dirname($this->stateFile);
        if (!is_dir($dir) && !mkdir($dir, 0700, true) && !is_dir($dir)) {
            throw new RuntimeException('Stockage privé indisponible.');
        }
        $json = json_encode($state, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
        if (!is_string($json)) throw new RuntimeException('Encodage du registre impossible.');
        $tmp = $this->stateFile . '.tmp.' . bin2hex(random_bytes(6));
        if (file_put_contents($tmp, $json, LOCK_EX) === false) throw new RuntimeException('Écriture du registre impossible.');
        @chmod($tmp, 0600);
        if (!@rename($tmp, $this->stateFile)) { @unlink($tmp); throw new RuntimeException('Finalisation du registre impossible.'); }
        @chmod($this->stateFile, 0600);
    }
}
