<?php
declare(strict_types=1);

namespace App\Workflow;

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

final class WorkflowRepository
{
    private PDO $pdo;
    public function __construct() { $this->pdo = Database::connection(); }

    public function workflows(): array { return $this->pdo->query("SELECT w.*, (SELECT COUNT(*) FROM axiom_workflow_states s WHERE s.workflow_id=w.id AND s.archived_at IS NULL) state_count, (SELECT COUNT(*) FROM axiom_runtime_instances i WHERE i.workflow_id=w.id AND i.completed_at IS NULL) active_instances FROM axiom_workflows w WHERE w.archived_at IS NULL ORDER BY w.name")->fetchAll() ?: []; }
    public function workflow(int $id): ?array { $s=$this->pdo->prepare('SELECT * FROM axiom_workflows WHERE id=?'); $s->execute([$id]); return $s->fetch() ?: null; }
    public function states(int $workflowId): array { $s=$this->pdo->prepare('SELECT * FROM axiom_workflow_states WHERE workflow_id=? AND archived_at IS NULL ORDER BY sort_order,id'); $s->execute([$workflowId]); return $s->fetchAll() ?: []; }
    public function transitions(int $workflowId): array { $s=$this->pdo->prepare("SELECT t.*, fs.name from_name, ts.name to_name FROM axiom_workflow_transitions t JOIN axiom_workflow_states fs ON fs.id=t.from_state_id JOIN axiom_workflow_states ts ON ts.id=t.to_state_id WHERE t.workflow_id=? AND t.archived_at IS NULL ORDER BY t.name"); $s->execute([$workflowId]); return $s->fetchAll() ?: []; }
    public function objects(): array { return $this->pdo->query("SELECT uuid,reference,label,status FROM axiom_objects WHERE archived_at IS NULL ORDER BY updated_at DESC LIMIT 300")->fetchAll() ?: []; }
    public function instances(): array { return $this->pdo->query("SELECT i.*,w.name workflow_name,s.name state_name,o.reference object_reference,o.label object_label FROM axiom_runtime_instances i JOIN axiom_workflows w ON w.id=i.workflow_id JOIN axiom_workflow_states s ON s.id=i.current_state_id JOIN axiom_objects o ON o.uuid COLLATE utf8mb4_unicode_ci=i.object_uuid COLLATE utf8mb4_unicode_ci ORDER BY i.updated_at DESC LIMIT 200")->fetchAll() ?: []; }
    public function tasks(bool $includeCompleted): array { $sql="SELECT t.*,i.object_uuid,o.reference object_reference,o.label object_label,w.name workflow_name FROM axiom_runtime_tasks t LEFT JOIN axiom_runtime_instances i ON i.id=t.workflow_instance_id LEFT JOIN axiom_objects o ON o.uuid COLLATE utf8mb4_unicode_ci=i.object_uuid COLLATE utf8mb4_unicode_ci LEFT JOIN axiom_workflows w ON w.id=i.workflow_id" . ($includeCompleted?'':' WHERE t.completed_at IS NULL') . " ORDER BY t.due_at IS NULL,t.due_at,t.created_at DESC LIMIT 250"; return $this->pdo->query($sql)->fetchAll() ?: []; }

    public function saveWorkflow(array $input): int
    {
        $code=$this->code((string)($input['code']??'')); $name=trim((string)($input['name']??'')); $desc=trim((string)($input['description']??''));
        if($code===''||$name==='') throw new InvalidArgumentException('Le code et le nom du workflow sont obligatoires.');
        $s=$this->pdo->prepare('INSERT INTO axiom_workflows(code,name,description,version_no,active) VALUES(?,?,?,1,1) ON DUPLICATE KEY UPDATE name=VALUES(name),description=VALUES(description),active=1,updated_at=NOW()');
        $s->execute([$code,$name,$desc]);
        $id=(int)$this->pdo->lastInsertId(); if($id===0){$q=$this->pdo->prepare('SELECT id FROM axiom_workflows WHERE code=?');$q->execute([$code]);$id=(int)$q->fetchColumn();}
        return $id;
    }

    public function saveState(array $input): int
    {
        $wid=max(0,(int)($input['workflow_id']??0)); $code=$this->code((string)($input['code']??'')); $name=trim((string)($input['name']??''));
        if($wid<=0||$code===''||$name==='') throw new InvalidArgumentException('Workflow, code et nom de l’état obligatoires.');
        $initial=!empty($input['is_initial'])?1:0; $final=!empty($input['is_final'])?1:0; $sort=max(0,(int)($input['sort_order']??0));
        $this->pdo->beginTransaction();
        try{
            if($initial){$s=$this->pdo->prepare('UPDATE axiom_workflow_states SET is_initial=0 WHERE workflow_id=?');$s->execute([$wid]);}
            $s=$this->pdo->prepare('INSERT INTO axiom_workflow_states(workflow_id,code,name,is_initial,is_final,sort_order) VALUES(?,?,?,?,?,?) ON DUPLICATE KEY UPDATE name=VALUES(name),is_initial=VALUES(is_initial),is_final=VALUES(is_final),sort_order=VALUES(sort_order),archived_at=NULL');
            $s->execute([$wid,$code,$name,$initial,$final,$sort]);
            $id=(int)$this->pdo->lastInsertId(); if(!$id){$q=$this->pdo->prepare('SELECT id FROM axiom_workflow_states WHERE workflow_id=? AND code=?');$q->execute([$wid,$code]);$id=(int)$q->fetchColumn();}
            $this->pdo->commit(); return $id;
        }catch(Throwable $e){if($this->pdo->inTransaction())$this->pdo->rollBack();throw $e;}
    }

    public function saveTransition(array $input): int
    {
        $wid=max(0,(int)($input['workflow_id']??0));$code=$this->code((string)($input['code']??''));$name=trim((string)($input['name']??''));$from=max(0,(int)($input['from_state_id']??0));$to=max(0,(int)($input['to_state_id']??0));
        if($wid<=0||$code===''||$name===''||$from<=0||$to<=0) throw new InvalidArgumentException('Tous les champs principaux de la transition sont obligatoires.');
        $permission=trim((string)($input['permission_code']??''))?:null;$task=trim((string)($input['task_title']??''))?:null;$role=trim((string)($input['task_role_code']??''))?:null;$days=max(0,(int)($input['task_due_days']??0));
        $s=$this->pdo->prepare('INSERT INTO axiom_workflow_transitions(workflow_id,code,name,from_state_id,to_state_id,permission_code,task_title,task_role_code,task_due_days) VALUES(?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE name=VALUES(name),from_state_id=VALUES(from_state_id),to_state_id=VALUES(to_state_id),permission_code=VALUES(permission_code),task_title=VALUES(task_title),task_role_code=VALUES(task_role_code),task_due_days=VALUES(task_due_days),archived_at=NULL');
        $s->execute([$wid,$code,$name,$from,$to,$permission,$task,$role,$days]); return (int)$this->pdo->lastInsertId();
    }

    public function startInstance(array $input, ?int $userId): int
    {
        $wid=max(0,(int)($input['workflow_id']??0));$uuid=trim((string)($input['object_uuid']??''));
        if($wid<=0||$uuid==='') throw new InvalidArgumentException('Workflow et objet obligatoires.');
        $q=$this->pdo->prepare('SELECT id,is_final FROM axiom_workflow_states WHERE workflow_id=? AND is_initial=1 AND archived_at IS NULL LIMIT 1');$q->execute([$wid]);$state=$q->fetch();
        if(!$state) throw new InvalidArgumentException('Ce workflow ne possède aucun état initial.');
        $this->pdo->beginTransaction();
        try{
            $s=$this->pdo->prepare('INSERT INTO axiom_runtime_instances(workflow_id,object_uuid,current_state_id,started_by,updated_by) VALUES(?,?,?,?,?)');$s->execute([$wid,$uuid,(int)$state['id'],$userId,$userId]);$id=(int)$this->pdo->lastInsertId();
            $this->history($id,null,(int)$state['id'],null,'Workflow démarré',$userId);
            $this->objectEvent($uuid,'workflow.started','Workflow démarré',$userId,['instance_id'=>$id,'workflow_id'=>$wid]);
            $this->pdo->commit(); return $id;
        }catch(Throwable $e){if($this->pdo->inTransaction())$this->pdo->rollBack();throw $e;}
    }

    public function applyTransition(array $input, ?int $userId): int
    {
        $iid=max(0,(int)($input['instance_id']??0));$tid=max(0,(int)($input['transition_id']??0));$note=trim((string)($input['note']??''));
        $q=$this->pdo->prepare('SELECT i.*,t.from_state_id,t.to_state_id,t.name transition_name,t.task_title,t.task_role_code,t.task_due_days,s.is_final FROM axiom_runtime_instances i JOIN axiom_workflow_transitions t ON t.id=? AND t.workflow_id=i.workflow_id JOIN axiom_workflow_states s ON s.id=t.to_state_id WHERE i.id=?');$q->execute([$tid,$iid]);$row=$q->fetch();
        if(!$row) throw new InvalidArgumentException('Instance ou transition introuvable.');
        if((int)$row['current_state_id']!==(int)$row['from_state_id']) throw new InvalidArgumentException('Cette transition ne correspond pas à l’état courant.');
        if($row['completed_at']!==null) throw new InvalidArgumentException('Cette instance est déjà terminée.');
        $this->pdo->beginTransaction();
        try{
            $completed=((int)$row['is_final']===1)?date('Y-m-d H:i:s'):null;
            $s=$this->pdo->prepare('UPDATE axiom_runtime_instances SET current_state_id=?,updated_by=?,completed_at=?,updated_at=NOW() WHERE id=?');$s->execute([(int)$row['to_state_id'],$userId,$completed,$iid]);
            $this->history($iid,(int)$row['from_state_id'],(int)$row['to_state_id'],$tid,$note?:$row['transition_name'],$userId);
            if(!empty($row['task_title'])){$due=((int)$row['task_due_days']>0)?date('Y-m-d H:i:s',strtotime('+'.(int)$row['task_due_days'].' days')):null;$s=$this->pdo->prepare('INSERT INTO axiom_runtime_tasks(workflow_instance_id,title,assigned_role_code,due_at,created_by) VALUES(?,?,?,?,?)');$s->execute([$iid,$row['task_title'],$row['task_role_code'],$due,$userId]);}
            $this->objectEvent((string)$row['object_uuid'],'workflow.transition',$row['transition_name'],$userId,['instance_id'=>$iid,'transition_id'=>$tid]);
            $this->pdo->commit(); return $iid;
        }catch(Throwable $e){if($this->pdo->inTransaction())$this->pdo->rollBack();throw $e;}
    }

    public function completeTask(int $taskId, ?int $userId): void
    {
        if($taskId<=0) throw new InvalidArgumentException('Tâche invalide.');
        $s=$this->pdo->prepare('UPDATE axiom_runtime_tasks SET status=\'completed\',completed_by=?,completed_at=NOW(),updated_at=NOW() WHERE id=? AND completed_at IS NULL');$s->execute([$userId,$taskId]);
        if($s->rowCount()===0) throw new InvalidArgumentException('Tâche introuvable ou déjà terminée.');
    }

    public function availableTransitions(int $instanceId): array
    {
        $s=$this->pdo->prepare('SELECT t.* FROM axiom_runtime_instances i JOIN axiom_workflow_transitions t ON t.workflow_id=i.workflow_id AND t.from_state_id=i.current_state_id AND t.archived_at IS NULL WHERE i.id=? AND i.completed_at IS NULL ORDER BY t.name');$s->execute([$instanceId]);return $s->fetchAll()?:[];
    }

    private function history(int $iid,?int $from,int $to,?int $transition,string $note,?int $userId):void{$s=$this->pdo->prepare('INSERT INTO axiom_runtime_history(instance_id,from_state_id,to_state_id,transition_id,note,actor_user_id) VALUES(?,?,?,?,?,?)');$s->execute([$iid,$from,$to,$transition,$note,$userId]);}
    private function objectEvent(string $uuid,string $type,string $label,?int $userId,array $data):void{$s=$this->pdo->prepare('INSERT INTO axiom_object_events(object_uuid,event_type,label,data_json,actor_user_id) VALUES(?,?,?,?,?)');$s->execute([$uuid,$type,$label,json_encode($data,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),$userId]);}
    private function code(string $value):string{$v=strtolower(trim($value));$v=preg_replace('/[^a-z0-9._-]+/','-',$v)??'';return trim($v,'-');}
}
