<?php
declare(strict_types=1);

namespace App\Services;

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

final class StudioAutomationService
{
    private const TRIGGERS=['manual','scheduled','record_created','record_updated','record_deleted','validation','api','webhook'];
    private const STEPS=['rule','workflow','notification','email','api','webhook','document','task','delay','audit'];

    public function modules(): array { return Database::connection()->query("SELECT id,name,code FROM axiom_studio_modules WHERE archived_at IS NULL ORDER BY name")->fetchAll(); }
    public function automations(int $moduleId): array { $q=Database::connection()->prepare("SELECT * FROM axiom_automations WHERE module_id=:m AND archived_at IS NULL ORDER BY priority,name");$q->execute(['m'=>$moduleId]);return $q->fetchAll(); }
    public function automation(int $id): ?array { $q=Database::connection()->prepare("SELECT * FROM axiom_automations WHERE id=:id AND archived_at IS NULL");$q->execute(['id'=>$id]);$r=$q->fetch();return $r?:null; }
    public function steps(int $id): array { $q=Database::connection()->prepare("SELECT * FROM axiom_automation_steps WHERE automation_id=:id AND archived_at IS NULL ORDER BY position,id");$q->execute(['id'=>$id]);$rows=$q->fetchAll();foreach($rows as &$r){$r['config']=json_decode((string)($r['configuration_json']??''),true)?:[];}return $rows; }
    public function runs(int $id): array { $q=Database::connection()->prepare("SELECT * FROM axiom_automation_runs WHERE automation_id=:id ORDER BY id DESC LIMIT 30");$q->execute(['id'=>$id]);return $q->fetchAll(); }
    public function queue(): array { return Database::connection()->query("SELECT q.*,a.name automation_name FROM axiom_job_queue q LEFT JOIN axiom_automations a ON a.id=q.automation_id ORDER BY FIELD(q.status,'running','pending','failed','done'),q.priority,q.id DESC LIMIT 50")->fetchAll(); }
    public function notifications(): array { return Database::connection()->query("SELECT * FROM axiom_notifications ORDER BY id DESC LIMIT 30")->fetchAll(); }
    public function triggerTypes(): array { return ['manual'=>'Manuel','scheduled'=>'Planifié','record_created'=>'Création','record_updated'=>'Modification','record_deleted'=>'Suppression','validation'=>'Validation','api'=>'API','webhook'=>'Webhook']; }
    public function stepTypes(): array { return ['rule'=>'Exécuter une règle','workflow'=>'Déclencher un workflow','notification'=>'Notification interne','email'=>'Préparer un e-mail','api'=>'Appeler une API','webhook'=>'Webhook','document'=>'Générer un document','task'=>'Créer une tâche','delay'=>'Temporisation','audit'=>'Écrire dans l’audit']; }

    public function saveAutomation(int $moduleId,array $data,?int $userId): int
    {
        if($moduleId<1) throw new InvalidArgumentException('Module obligatoire.');
        $id=(int)($data['automation_id']??0);$name=trim((string)($data['name']??''));if($name==='')throw new InvalidArgumentException('Nom obligatoire.');
        $trigger=(string)($data['trigger_type']??'manual');if(!in_array($trigger,self::TRIGGERS,true))$trigger='manual';
        $code=$this->code((string)($data['code']??$name));$schedule=trim((string)($data['schedule_expression']??''));
        $pdo=Database::connection();
        if($id>0){$q=$pdo->prepare("UPDATE axiom_automations SET name=:n,description=:d,trigger_type=:t,schedule_expression=:s,priority=:p,max_retries=:r,status='draft',updated_by=:u,updated_at=CURRENT_TIMESTAMP WHERE id=:id AND module_id=:m AND archived_at IS NULL");$q->execute(['n'=>$name,'d'=>trim((string)($data['description']??''))?:null,'t'=>$trigger,'s'=>$schedule?:null,'p'=>max(1,min(999,(int)($data['priority']??100))),'r'=>max(0,min(10,(int)($data['max_retries']??3))),'u'=>$userId,'id'=>$id,'m'=>$moduleId]);return $id;}
        $code=$this->uniqueCode($code);$q=$pdo->prepare("INSERT INTO axiom_automations(module_id,code,name,description,trigger_type,schedule_expression,priority,max_retries,status,current_version,created_by,updated_by) VALUES(:m,:c,:n,:d,:t,:s,:p,:r,'draft',1,:u,:u)");$q->execute(['m'=>$moduleId,'c'=>$code,'n'=>$name,'d'=>trim((string)($data['description']??''))?:null,'t'=>$trigger,'s'=>$schedule?:null,'p'=>max(1,min(999,(int)($data['priority']??100))),'r'=>max(0,min(10,(int)($data['max_retries']??3))),'u'=>$userId]);return (int)$pdo->lastInsertId();
    }

    public function addStep(int $automationId,array $data,?int $userId): int
    {
        if(!$this->automation($automationId))throw new RuntimeException('Automatisation introuvable.');$type=(string)($data['step_type']??'audit');if(!in_array($type,self::STEPS,true))throw new InvalidArgumentException('Type d’étape invalide.');
        $label=trim((string)($data['label']??''))?:($this->stepTypes()[$type]??$type);$config=['reference_id'=>(int)($data['reference_id']??0),'recipient'=>trim((string)($data['recipient']??'')),'subject'=>trim((string)($data['subject']??'')),'message'=>trim((string)($data['message']??'')),'url'=>trim((string)($data['url']??'')),'delay_seconds'=>max(0,(int)($data['delay_seconds']??0))];
        $q=Database::connection()->prepare("INSERT INTO axiom_automation_steps(automation_id,step_type,label,configuration_json,position,continue_on_error,created_by,updated_by) VALUES(:a,:t,:l,:j,:p,:c,:u,:u)");$q->execute(['a'=>$automationId,'t'=>$type,'l'=>$label,'j'=>json_encode($config,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),'p'=>max(0,(int)($data['position']??0)),'c'=>isset($data['continue_on_error'])?1:0,'u'=>$userId]);$this->touch($automationId,$userId);return (int)Database::connection()->lastInsertId();
    }
    public function archiveStep(int $id,?int $userId): void { Database::connection()->prepare("UPDATE axiom_automation_steps SET archived_at=CURRENT_TIMESTAMP,updated_by=:u WHERE id=:id")->execute(['u'=>$userId,'id'=>$id]); }
    public function publish(int $id,?int $userId): void { $a=$this->automation($id);if(!$a)throw new RuntimeException('Automatisation introuvable.');if(!$this->steps($id))throw new RuntimeException('Ajoutez au moins une étape.');$pdo=Database::connection();$pdo->beginTransaction();try{$snapshot=['automation'=>$a,'steps'=>$this->steps($id)];$v=(int)$a['current_version'];$pdo->prepare("INSERT INTO axiom_automation_versions(automation_id,version_number,snapshot_json,published_by,published_at) VALUES(:a,:v,:j,:u,CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE snapshot_json=VALUES(snapshot_json),published_by=VALUES(published_by),published_at=CURRENT_TIMESTAMP")->execute(['a'=>$id,'v'=>$v,'j'=>json_encode($snapshot,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),'u'=>$userId]);$pdo->prepare("UPDATE axiom_automations SET status='published',published_at=CURRENT_TIMESTAMP,updated_by=:u WHERE id=:id")->execute(['u'=>$userId,'id'=>$id]);$pdo->commit();}catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;} }
    public function archive(int $id,?int $userId): void { Database::connection()->prepare("UPDATE axiom_automations SET status='archived',archived_at=CURRENT_TIMESTAMP,updated_by=:u WHERE id=:id")->execute(['u'=>$userId,'id'=>$id]); }

    public function enqueue(int $id,array $payload,?int $userId): int
    {
        $a=$this->automation($id);if(!$a)throw new RuntimeException('Automatisation introuvable.');$q=Database::connection()->prepare("INSERT INTO axiom_job_queue(automation_id,status,priority,attempts,max_attempts,available_at,payload_json,created_by) VALUES(:a,'pending',:p,0,:m,CURRENT_TIMESTAMP,:j,:u)");$q->execute(['a'=>$id,'p'=>(int)$a['priority'],'m'=>max(1,(int)$a['max_retries']+1),'j'=>json_encode($payload,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),'u'=>$userId]);return (int)Database::connection()->lastInsertId();
    }

    public function runQueueJob(int $jobId,?int $userId): array
    {
        $pdo=Database::connection();$q=$pdo->prepare("SELECT * FROM axiom_job_queue WHERE id=:id");$q->execute(['id'=>$jobId]);$job=$q->fetch();if(!$job)throw new RuntimeException('Tâche introuvable.');
        $automation=$this->automation((int)$job['automation_id']);if(!$automation)throw new RuntimeException('Automatisation introuvable.');$payload=json_decode((string)$job['payload_json'],true)?:[];$started=microtime(true);
        $pdo->prepare("UPDATE axiom_job_queue SET status='running',started_at=CURRENT_TIMESTAMP,attempts=attempts+1 WHERE id=:id")->execute(['id'=>$jobId]);
        $run=$pdo->prepare("INSERT INTO axiom_automation_runs(automation_id,queue_job_id,trigger_type,status,input_json,started_by,started_at) VALUES(:a,:q,:t,'running',:i,:u,CURRENT_TIMESTAMP)");$run->execute(['a'=>$automation['id'],'q'=>$jobId,'t'=>'manual','i'=>json_encode($payload,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),'u'=>$userId]);$runId=(int)$pdo->lastInsertId();$results=[];$status='success';$error=null;
        try{foreach($this->steps((int)$automation['id']) as $step){$r=$this->executeStep($step,$payload,$userId);$results[]=$r;if(($r['status']??'')==='failed'&&!$step['continue_on_error'])throw new RuntimeException((string)($r['message']??'Étape en erreur.'));}}catch(Throwable $e){$status='failed';$error=$e->getMessage();}
        $duration=(int)round((microtime(true)-$started)*1000);$pdo->prepare("UPDATE axiom_automation_runs SET status=:s,result_json=:r,error_message=:e,duration_ms=:d,finished_at=CURRENT_TIMESTAMP WHERE id=:id")->execute(['s'=>$status,'r'=>json_encode($results,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),'e'=>$error,'d'=>$duration,'id'=>$runId]);
        $pdo->prepare("UPDATE axiom_job_queue SET status=:s,last_error=:e,finished_at=CURRENT_TIMESTAMP WHERE id=:id")->execute(['s'=>$status==='success'?'done':'failed','e'=>$error,'id'=>$jobId]);return ['status'=>$status,'duration_ms'=>$duration,'steps'=>$results,'error'=>$error];
    }

    private function executeStep(array $step,array $payload,?int $userId): array
    {
        $cfg=$step['config'];$type=(string)$step['step_type'];
        if($type==='notification'){$q=Database::connection()->prepare("INSERT INTO axiom_notifications(user_id,title,message,status,created_at) VALUES(:u,:t,:m,'unread',CURRENT_TIMESTAMP)");$q->execute(['u'=>$userId,'t'=>$cfg['subject']?:$step['label'],'m'=>$this->interpolate((string)($cfg['message']??''),$payload)]);return ['step_id'=>(int)$step['id'],'type'=>$type,'status'=>'success','message'=>'Notification créée.'];}
        if($type==='rule'&&!empty($cfg['reference_id'])){$result=(new StudioRuleService())->executeManual((int)$cfg['reference_id'],$payload,$userId);return ['step_id'=>(int)$step['id'],'type'=>$type,'status'=>'success','message'=>'Règle exécutée.','result'=>$result];}
        if($type==='delay')return ['step_id'=>(int)$step['id'],'type'=>$type,'status'=>'success','message'=>'Temporisation préparée ('.$cfg['delay_seconds'].' s).'];
        return ['step_id'=>(int)$step['id'],'type'=>$type,'status'=>'prepared','message'=>'Étape validée et préparée pour le connecteur correspondant.'];
    }
    private function interpolate(string $text,array $payload): string { return preg_replace_callback('/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/',static function($m)use($payload){$v=$payload;foreach(explode('.',$m[1]) as $k){if(!is_array($v)||!array_key_exists($k,$v))return $m[0];$v=$v[$k];}return is_scalar($v)?(string)$v:json_encode($v,JSON_UNESCAPED_UNICODE);},$text)??$text; }
    private function touch(int $id,?int $userId): void { Database::connection()->prepare("UPDATE axiom_automations SET status='draft',current_version=current_version+1,updated_by=:u WHERE id=:id")->execute(['u'=>$userId,'id'=>$id]); }
    private function code(string $v): string{$v=iconv('UTF-8','ASCII//TRANSLIT//IGNORE',$v)?:$v;$v=strtolower(trim(preg_replace('/[^a-zA-Z0-9]+/','_',$v),'_'));return substr($v?:'automation',0,110);}
    private function uniqueCode(string $base): string{$c=$base;$i=2;$q=Database::connection()->prepare("SELECT COUNT(*) FROM axiom_automations WHERE code=:c AND archived_at IS NULL");while(true){$q->execute(['c'=>$c]);if((int)$q->fetchColumn()===0)return $c;$c=$base.'_'.$i++;}}
}
