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

use App\Core\Database;
use InvalidArgumentException;
use PDO;

final class SecurityOperationsCenterService
{
    /** @param array<string,mixed> $filters @return array<string,mixed> */
    public function dashboard(array $filters): array
    {
        $pdo=Database::connection();
        $status=$this->allowed((string)($filters['status'] ?? ''),['open','investigating','resolved','false_positive']);
        $severity=$this->allowed((string)($filters['severity'] ?? ''),['low','medium','high','critical']);
        $module=mb_substr(trim((string)($filters['module'] ?? '')),0,80);
        $query=mb_substr(trim((string)($filters['q'] ?? '')),0,100);
        $days=max(1,min(90,(int)($filters['days'] ?? 30)));

        $summary=$pdo->prepare('SELECT COUNT(*) total, SUM(status=\'open\') open_count, SUM(status=\'investigating\') investigating_count, SUM(status=\'resolved\') resolved_count, SUM(status=\'false_positive\') false_positive_count, SUM(severity=\'critical\' AND status IN (\'open\',\'investigating\')) critical_active, COALESCE(MAX(CASE WHEN status IN (\'open\',\'investigating\') THEN risk_score ELSE 0 END),0) max_risk FROM system_security_incidents WHERE last_event_at>=DATE_SUB(NOW(),INTERVAL :days DAY)');
        $summary->bindValue('days',$days,PDO::PARAM_INT); $summary->execute();
        $stats=$summary->fetch() ?: [];

        $stmt=$pdo->prepare('SELECT i.id,i.incident_uuid,i.title,i.description,i.status,i.severity,i.risk_score,i.event_count,i.user_id,i.ip_hash,i.module,i.first_event_at,i.last_event_at,i.updated_at,r.name rule_name FROM system_security_incidents i INNER JOIN system_security_rules r ON r.id=i.rule_id WHERE i.last_event_at>=DATE_SUB(NOW(),INTERVAL :days DAY) AND (:status_filter=\'\' OR i.status=:status_match) AND (:severity_filter=\'\' OR i.severity=:severity_match) AND (:module_filter=\'\' OR i.module=:module_match) AND (:query_filter=\'\' OR i.title LIKE :query_like OR i.incident_uuid LIKE :query_like_uuid) ORDER BY FIELD(i.status,\'open\',\'investigating\',\'resolved\',\'false_positive\'),i.risk_score DESC,i.last_event_at DESC LIMIT 250');
        $stmt->bindValue('days',$days,PDO::PARAM_INT);
        $stmt->bindValue('status_filter',$status); $stmt->bindValue('status_match',$status);
        $stmt->bindValue('severity_filter',$severity); $stmt->bindValue('severity_match',$severity);
        $stmt->bindValue('module_filter',$module); $stmt->bindValue('module_match',$module);
        $stmt->bindValue('query_filter',$query); $stmt->bindValue('query_like','%'.$query.'%'); $stmt->bindValue('query_like_uuid','%'.$query.'%');
        $stmt->execute(); $incidents=$stmt->fetchAll() ?: [];

        $trend=$pdo->prepare('SELECT DATE(last_event_at) day,COUNT(*) incidents,COALESCE(AVG(risk_score),0) avg_risk,SUM(severity=\'critical\') critical_count FROM system_security_incidents WHERE last_event_at>=DATE_SUB(NOW(),INTERVAL 13 DAY) GROUP BY DATE(last_event_at) ORDER BY day ASC');
        $trend->execute();
        $modules=$pdo->prepare('SELECT COALESCE(NULLIF(module,\'\'),\'Non renseigné\') module,COUNT(*) incident_count,MAX(risk_score) max_risk FROM system_security_incidents WHERE last_event_at>=DATE_SUB(NOW(),INTERVAL :days DAY) GROUP BY COALESCE(NULLIF(module,\'\'),\'Non renseigné\') ORDER BY incident_count DESC,max_risk DESC LIMIT 10');
        $modules->bindValue('days',$days,PDO::PARAM_INT); $modules->execute();
        return ['generated_at'=>date('Y-m-d H:i:s'),'days'=>$days,'filters'=>compact('status','severity','module','query'),'stats'=>$stats,'incidents'=>$incidents,'trend'=>$trend->fetchAll() ?: [],'modules'=>$modules->fetchAll() ?: []];
    }

    /** @return array<string,mixed>|null */
    public function incident(int $id): ?array
    {
        if ($id<1) return null;
        $pdo=Database::connection();
        $stmt=$pdo->prepare('SELECT i.*,r.code rule_code,r.name rule_name,r.description rule_description FROM system_security_incidents i INNER JOIN system_security_rules r ON r.id=i.rule_id WHERE i.id=:id LIMIT 1');
        $stmt->execute(['id'=>$id]); $incident=$stmt->fetch();
        if (!$incident) return null;
        $events=$pdo->prepare('SELECT e.event_uuid,e.severity,e.category,e.module,e.action,e.result,e.user_id,e.session_reference,e.ip_hash,e.object_type,e.object_reference,e.occurred_at,e.metadata_json FROM system_security_incident_events l INNER JOIN system_security_events e ON e.event_uuid=l.event_uuid WHERE l.incident_uuid=:uuid ORDER BY e.occurred_at ASC LIMIT 500');
        $events->execute(['uuid'=>$incident['incident_uuid']]);
        $history=$pdo->prepare('SELECT h.old_status,h.new_status,h.note,h.changed_by,h.changed_at FROM system_security_incident_history h WHERE h.incident_uuid=:uuid ORDER BY h.changed_at DESC,h.id DESC LIMIT 100');
        $history->execute(['uuid'=>$incident['incident_uuid']]);
        $incident['events']=$events->fetchAll() ?: [];
        $incident['history']=$history->fetchAll() ?: [];
        $actions=$pdo->prepare('SELECT action_type,content,created_by,created_at FROM system_security_incident_actions WHERE incident_uuid=:uuid ORDER BY created_at DESC,id DESC LIMIT 250');
        $actions->execute(['uuid'=>$incident['incident_uuid']]);
        $incident['actions']=$actions->fetchAll() ?: [];
        $users=$pdo->prepare('SELECT id,nom,prenom,email FROM users WHERE actif=1 ORDER BY nom,prenom LIMIT 500');
        $users->execute();
        $incident['assignable_users']=$users->fetchAll() ?: [];
        return $incident;
    }

    public function updateIncident(int $id,string $status,string $note,int $userId): void
    {
        $status=$this->allowed($status,['open','investigating','resolved','false_positive']);
        if ($id<1 || $status==='') throw new InvalidArgumentException('Incident ou statut invalide.');
        $pdo=Database::connection(); $pdo->beginTransaction();
        try {
            $lock=$pdo->prepare('SELECT incident_uuid,status FROM system_security_incidents WHERE id=:id FOR UPDATE');
            $lock->execute(['id'=>$id]); $row=$lock->fetch();
            if (!$row) throw new InvalidArgumentException('Incident introuvable.');
            $update=$pdo->prepare('UPDATE system_security_incidents SET status=:status,resolved_at=CASE WHEN :resolved_status IN (\'resolved\',\'false_positive\') THEN NOW() ELSE NULL END,closed_reason=CASE WHEN :resolved_reason IN (\'resolved\',\'false_positive\') THEN :closed_note ELSE closed_reason END,updated_at=NOW() WHERE id=:id');
            $update->execute(['status'=>$status,'resolved_status'=>$status,'resolved_reason'=>$status,'closed_note'=>mb_substr($note,0,1000),'id'=>$id]);
            $history=$pdo->prepare('INSERT INTO system_security_incident_history (incident_uuid,old_status,new_status,note,changed_by,changed_at) VALUES (:uuid,:old_status,:new_status,:note,:changed_by,NOW())');
            $history->execute(['uuid'=>$row['incident_uuid'],'old_status'=>$row['status'],'new_status'=>$status,'note'=>mb_substr($note,0,1000),'changed_by'=>$userId>0?$userId:null]);
            $pdo->commit();
        } catch (\Throwable $e) { if ($pdo->inTransaction()) $pdo->rollBack(); throw $e; }
    }


    public function assignIncident(int $id,int $assigneeId,int $actorId): void
    {
        if ($id<1) throw new InvalidArgumentException('Incident invalide.');
        $pdo=Database::connection();
        $stmt=$pdo->prepare('UPDATE system_security_incidents SET assigned_to=:assigned_to,assigned_at=CASE WHEN :assigned_check>0 THEN NOW() ELSE NULL END,updated_at=NOW() WHERE id=:id');
        $stmt->bindValue('assigned_to',$assigneeId>0?$assigneeId:null,$assigneeId>0?PDO::PARAM_INT:PDO::PARAM_NULL);
        $stmt->bindValue('assigned_check',$assigneeId,PDO::PARAM_INT);
        $stmt->bindValue('id',$id,PDO::PARAM_INT);
        $stmt->execute();
        if ($stmt->rowCount()<1) throw new InvalidArgumentException('Incident introuvable.');
        $this->recordAction($pdo,$id,'assignment',$assigneeId>0?'Incident affecté à l’utilisateur #'.$assigneeId:'Affectation retirée',$actorId);
    }

    public function addResponseAction(int $id,string $type,string $content,int $actorId): void
    {
        $type=$this->allowed($type,['note','containment','eradication','recovery','evidence','escalation']);
        $content=trim($content);
        if ($id<1 || $type==='' || $content==='') throw new InvalidArgumentException('Action de réponse incomplète.');
        if (mb_strlen($content)>4000) throw new InvalidArgumentException('Le contenu est limité à 4 000 caractères.');
        $pdo=Database::connection();
        $this->recordAction($pdo,$id,$type,$content,$actorId);
    }

    public function reopenIncident(int $id,string $reason,int $actorId): void
    {
        $reason=trim($reason);
        if ($id<1 || $reason==='') throw new InvalidArgumentException('Le motif de réouverture est obligatoire.');
        $pdo=Database::connection(); $pdo->beginTransaction();
        try {
            $lock=$pdo->prepare('SELECT incident_uuid,status FROM system_security_incidents WHERE id=:id FOR UPDATE');
            $lock->execute(['id'=>$id]); $row=$lock->fetch();
            if (!$row) throw new InvalidArgumentException('Incident introuvable.');
            $update=$pdo->prepare("UPDATE system_security_incidents SET status='investigating',resolved_at=NULL,closed_reason=NULL,updated_at=NOW() WHERE id=:id");
            $update->execute(['id'=>$id]);
            $history=$pdo->prepare("INSERT INTO system_security_incident_history (incident_uuid,old_status,new_status,note,changed_by,changed_at) VALUES (:uuid,:old_status,'investigating',:note,:changed_by,NOW())");
            $history->execute(['uuid'=>$row['incident_uuid'],'old_status'=>$row['status'],'note'=>mb_substr($reason,0,1000),'changed_by'=>$actorId>0?$actorId:null]);
            $this->recordAction($pdo,$id,'escalation','Réouverture : '.$reason,$actorId);
            $pdo->commit();
        } catch (\Throwable $e) { if ($pdo->inTransaction()) $pdo->rollBack(); throw $e; }
    }

    private function recordAction(PDO $pdo,int $incidentId,string $type,string $content,int $actorId): void
    {
        $incident=$pdo->prepare('SELECT incident_uuid FROM system_security_incidents WHERE id=:id LIMIT 1');
        $incident->execute(['id'=>$incidentId]); $uuid=$incident->fetchColumn();
        if (!is_string($uuid) || $uuid==='') throw new InvalidArgumentException('Incident introuvable.');
        $stmt=$pdo->prepare('INSERT INTO system_security_incident_actions (incident_uuid,action_type,content,created_by,created_at) VALUES (:uuid,:action_type,:content,:created_by,NOW())');
        $stmt->execute(['uuid'=>$uuid,'action_type'=>$type,'content'=>mb_substr($content,0,4000),'created_by'=>$actorId>0?$actorId:null]);
    }

    /** @param list<string> $values */
    private function allowed(string $value,array $values): string { return in_array($value,$values,true)?$value:''; }
}
