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

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

final class SocPlaybookService
{
    /** @return array<string,mixed> */
    public function dashboard(): array
    {
        $pdo=Database::connection();
        $playbooks=$pdo->prepare('SELECT p.id,p.code,p.name,p.description,p.trigger_severity,p.trigger_score_min,p.requires_human_approval,p.is_active,p.created_at,COUNT(s.id) step_count FROM system_soc_playbooks p LEFT JOIN system_soc_playbook_steps s ON s.playbook_id=p.id GROUP BY p.id,p.code,p.name,p.description,p.trigger_severity,p.trigger_score_min,p.requires_human_approval,p.is_active,p.created_at ORDER BY p.name');
        $playbooks->execute();
        $runs=$pdo->prepare('SELECT r.id,r.run_uuid,r.status,r.current_step,r.started_at,r.updated_at,r.completed_at,r.error_message,p.name playbook_name,i.id incident_id,i.incident_uuid,i.title incident_title,i.severity incident_severity,i.risk_score FROM system_soc_playbook_runs r INNER JOIN system_soc_playbooks p ON p.id=r.playbook_id INNER JOIN system_security_incidents i ON i.id=r.incident_id ORDER BY r.updated_at DESC,r.id DESC LIMIT 100');
        $runs->execute();
        $incidents=$pdo->prepare("SELECT id,incident_uuid,title,severity,risk_score,status FROM system_security_incidents WHERE status IN ('open','investigating') ORDER BY risk_score DESC,last_event_at DESC LIMIT 250");
        $incidents->execute();
        return ['playbooks'=>$playbooks->fetchAll() ?: [],'runs'=>$runs->fetchAll() ?: [],'incidents'=>$incidents->fetchAll() ?: []];
    }

    /** @return array<string,mixed>|null */
    public function run(int $id): ?array
    {
        if($id<1)return null;
        $pdo=Database::connection();
        $stmt=$pdo->prepare('SELECT r.*,p.name playbook_name,p.description playbook_description,i.title incident_title,i.incident_uuid,i.severity incident_severity,i.risk_score FROM system_soc_playbook_runs r INNER JOIN system_soc_playbooks p ON p.id=r.playbook_id INNER JOIN system_security_incidents i ON i.id=r.incident_id WHERE r.id=:id LIMIT 1');
        $stmt->execute(['id'=>$id]); $run=$stmt->fetch();
        if(!$run)return null;
        $steps=$pdo->prepare('SELECT rs.id,rs.step_order,rs.status,rs.started_at,rs.completed_at,rs.output_text,rs.error_message,s.name,s.description,s.action_type,s.requires_approval FROM system_soc_playbook_run_steps rs INNER JOIN system_soc_playbook_steps s ON s.id=rs.playbook_step_id WHERE rs.run_id=:run_id ORDER BY rs.step_order');
        $steps->execute(['run_id'=>$id]); $run['steps']=$steps->fetchAll() ?: [];
        return $run;
    }

    public function start(int $playbookId,int $incidentId,int $actorId): int
    {
        if($playbookId<1||$incidentId<1)throw new InvalidArgumentException('Playbook ou incident invalide.');
        $pdo=Database::connection(); $pdo->beginTransaction();
        try{
            $pb=$pdo->prepare('SELECT id,is_active FROM system_soc_playbooks WHERE id=:id LIMIT 1 FOR UPDATE');
            $pb->execute(['id'=>$playbookId]); $playbook=$pb->fetch();
            if(!$playbook || (int)$playbook['is_active']!==1)throw new InvalidArgumentException('Playbook indisponible.');
            $incident=$pdo->prepare('SELECT id FROM system_security_incidents WHERE id=:id LIMIT 1');
            $incident->execute(['id'=>$incidentId]); if(!$incident->fetch())throw new InvalidArgumentException('Incident introuvable.');
            $uuid=$this->uuid();
            $insert=$pdo->prepare("INSERT INTO system_soc_playbook_runs (run_uuid,playbook_id,incident_id,status,current_step,started_by,started_at,updated_at) VALUES (:uuid,:playbook_id,:incident_id,'running',1,:started_by,NOW(),NOW())");
            $insert->execute(['uuid'=>$uuid,'playbook_id'=>$playbookId,'incident_id'=>$incidentId,'started_by'=>$actorId>0?$actorId:null]);
            $runId=(int)$pdo->lastInsertId();
            $steps=$pdo->prepare('INSERT INTO system_soc_playbook_run_steps (run_id,playbook_step_id,step_order,status) SELECT :run_id,id,step_order,CASE WHEN step_order=1 THEN \'waiting_approval\' ELSE \'pending\' END FROM system_soc_playbook_steps WHERE playbook_id=:playbook_id ORDER BY step_order');
            $steps->execute(['run_id'=>$runId,'playbook_id'=>$playbookId]);
            $pdo->commit(); return $runId;
        }catch(\Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
    }

    public function executeCurrent(int $runId,string $decision,string $note,int $actorId): void
    {
        $decision=$this->allowed($decision,['approve','skip']);
        if($runId<1||$decision==='')throw new InvalidArgumentException('Décision invalide.');
        $note=mb_substr(trim($note),0,2000);
        $pdo=Database::connection(); $pdo->beginTransaction();
        try{
            $runStmt=$pdo->prepare("SELECT id,status,current_step,incident_id FROM system_soc_playbook_runs WHERE id=:id FOR UPDATE");
            $runStmt->execute(['id'=>$runId]); $run=$runStmt->fetch();
            if(!$run || !in_array($run['status'],['running','paused'],true))throw new InvalidArgumentException('Exécution indisponible.');
            $stepStmt=$pdo->prepare('SELECT rs.id,rs.step_order,s.action_type,s.name,s.description FROM system_soc_playbook_run_steps rs INNER JOIN system_soc_playbook_steps s ON s.id=rs.playbook_step_id WHERE rs.run_id=:run_id AND rs.step_order=:step_order LIMIT 1 FOR UPDATE');
            $stepStmt->execute(['run_id'=>$runId,'step_order'=>(int)$run['current_step']]); $step=$stepStmt->fetch();
            if(!$step)throw new InvalidArgumentException('Étape introuvable.');
            $status=$decision==='approve'?'completed':'skipped';
            $output=$decision==='approve'?$this->safeAction($pdo,(int)$run['incident_id'],(string)$step['action_type'],$actorId):'Étape ignorée par validation humaine.';
            if($note!=='')$output.="\nNote : ".$note;
            $updateStep=$pdo->prepare('UPDATE system_soc_playbook_run_steps SET status=:status,approved_by=:approved_by,started_at=COALESCE(started_at,NOW()),completed_at=NOW(),output_text=:output WHERE id=:id');
            $updateStep->execute(['status'=>$status,'approved_by'=>$actorId>0?$actorId:null,'output'=>$output,'id'=>$step['id']]);
            $next=(int)$run['current_step']+1;
            $nextStmt=$pdo->prepare('SELECT id FROM system_soc_playbook_run_steps WHERE run_id=:run_id AND step_order=:step_order LIMIT 1');
            $nextStmt->execute(['run_id'=>$runId,'step_order'=>$next]);
            if($nextStmt->fetch()){
                $activate=$pdo->prepare("UPDATE system_soc_playbook_run_steps SET status='waiting_approval' WHERE run_id=:run_id AND step_order=:step_order");
                $activate->execute(['run_id'=>$runId,'step_order'=>$next]);
                $updateRun=$pdo->prepare("UPDATE system_soc_playbook_runs SET status='running',current_step=:current_step,updated_at=NOW() WHERE id=:id");
                $updateRun->execute(['current_step'=>$next,'id'=>$runId]);
            }else{
                $complete=$pdo->prepare("UPDATE system_soc_playbook_runs SET status='completed',completed_at=NOW(),updated_at=NOW() WHERE id=:id");
                $complete->execute(['id'=>$runId]);
            }
            $pdo->commit();
        }catch(\Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
    }

    public function changeStatus(int $runId,string $status,int $actorId): void
    {
        $status=$this->allowed($status,['paused','running','cancelled']);
        if($runId<1||$status==='')throw new InvalidArgumentException('Statut invalide.');
        $pdo=Database::connection();
        $stmt=$pdo->prepare("UPDATE system_soc_playbook_runs SET status=:status,cancelled_by=CASE WHEN :cancel_check='cancelled' THEN :actor_id ELSE cancelled_by END,completed_at=CASE WHEN :complete_check='cancelled' THEN NOW() ELSE completed_at END,updated_at=NOW() WHERE id=:id AND status IN ('running','paused')");
        $stmt->execute(['status'=>$status,'cancel_check'=>$status,'actor_id'=>$actorId>0?$actorId:null,'complete_check'=>$status,'id'=>$runId]);
        if($stmt->rowCount()<1)throw new InvalidArgumentException('Exécution introuvable ou déjà terminée.');
    }

    private function safeAction(PDO $pdo,int $incidentId,string $actionType,int $actorId): string
    {
        if($actionType==='assign_owner'){
            $stmt=$pdo->prepare('UPDATE system_security_incidents SET assigned_to=COALESCE(assigned_to,:actor_id),assigned_at=COALESCE(assigned_at,NOW()),updated_at=NOW() WHERE id=:id');
            $stmt->execute(['actor_id'=>$actorId>0?$actorId:null,'id'=>$incidentId]);
            return 'Responsable confirmé sans écraser une affectation existante.';
        }
        if($actionType==='set_due_date'){
            $stmt=$pdo->prepare('UPDATE system_security_incidents SET due_at=COALESCE(due_at,DATE_ADD(NOW(),INTERVAL 24 HOUR)),updated_at=NOW() WHERE id=:id');
            $stmt->execute(['id'=>$incidentId]);
            return 'Échéance de traitement proposée à 24 heures si elle était absente.';
        }
        if($actionType==='escalate'){
            $stmt=$pdo->prepare('UPDATE system_security_incidents SET escalation_level=LEAST(5,escalation_level+1),updated_at=NOW() WHERE id=:id');
            $stmt->execute(['id'=>$incidentId]);
            return 'Niveau d’escalade augmenté après validation humaine.';
        }
        if($actionType==='record_note')return 'Recommandation consignée. Aucune action système destructive exécutée.';
        if($actionType==='request_review')return 'Revue humaine demandée. Aucun compte ni accès n’a été bloqué automatiquement.';
        return 'Étape validée en mode conseil sécurisé.';
    }

    private function allowed(string $value,array $allowed): string{return in_array($value,$allowed,true)?$value:'';}
    private function uuid(): string{$b=random_bytes(16);$b[6]=chr((ord($b[6])&15)|64);$b[8]=chr((ord($b[8])&63)|128);$h=bin2hex($b);return substr($h,0,8).'-'.substr($h,8,4).'-'.substr($h,12,4).'-'.substr($h,16,4).'-'.substr($h,20);}
}
