<?php
declare(strict_types=1);

namespace App\Services;

use App\Core\Audit\AuditLogger;
use App\Core\Database;
use RuntimeException;
use Throwable;

final class ReleaseAutomationEngine
{
    /** @var array<string, bool> */
    private static array $running = [];

    public function executeRule(array $rule, array $context, int $actorId, bool $simulation = false, ?string $executionUuid = null): array
    {
        $executionUuid ??= $this->uuidV4();
        $guard = (int)$rule['organisation_id'] . ':' . (int)$rule['id'] . ':' . $executionUuid;
        if (isset(self::$running[$guard])) throw new RuntimeException('Boucle d’automatisation détectée.');
        self::$running[$guard] = true;
        $started = microtime(true);
        $status = 'success';
        $message = 'Simulation terminée sans modification.';
        try {
            if (!$simulation) {
                if ((int)$rule['is_active'] !== 1) throw new RuntimeException('Règle inactive.');
                $message = $this->applyAction($rule, $context, $actorId);
            }
        } catch (Throwable $exception) {
            $status = 'failed';
            $message = $exception->getMessage();
        } finally {
            unset(self::$running[$guard]);
        }
        $duration = max(0, (int)round((microtime(true) - $started) * 1000));
        $this->writeLog($rule, $executionUuid, $simulation, $status, $duration, $message, $context, $actorId);
        if ($status === 'failed') throw new RuntimeException($message);
        return ['execution_uuid'=>$executionUuid,'status'=>$status,'simulation'=>$simulation,'duration_ms'=>$duration,'message'=>$message];
    }

    private function applyAction(array $rule, array $context, int $actorId): string
    {
        try {
            $parameters = json_decode((string)($rule['parameters_json'] ?? '{}'), true, 16, JSON_THROW_ON_ERROR);
        } catch (Throwable) {
            $parameters = [];
        }
        if (!is_array($parameters)) $parameters = [];
        $action = (string)$rule['action_type'];
        if ($action === 'audit_log') {
            (new AuditLogger())->record('release_automation.executed', 'release_automation', 'automation_rule', (int)$rule['id'], null,
                ['context'=>$context,'actor_id'=>$actorId?:null]);
            return 'Événement écrit dans le journal d’audit.';
        }
        if ($action === 'set_release_status') {
            $status = (string)($parameters['status'] ?? '');
            if (!in_array($status, ['draft','planned','active','completed','cancelled'], true)) throw new RuntimeException('Statut cible invalide.');
            $statement = Database::connection()->prepare(
                'UPDATE axiom_release_trains SET status = :status, updated_by = :actor_id, updated_at = NOW() '
                . 'WHERE id = :train_id AND organisation_id = :organisation_id AND archived_at IS NULL'
            );
            $statement->execute(['status'=>$status,'actor_id'=>$actorId?:null,'train_id'=>(int)$rule['release_train_id'],
                'organisation_id'=>(int)$rule['organisation_id']]);
            return 'Statut du Release Train mis à jour.';
        }
        if ($action === 'set_release_progress') {
            $progress = max(0, min(100, (int)($parameters['progress'] ?? 0)));
            (new AuditLogger())->record('release_automation.progress_requested', 'release_automation', 'release_train',
                (int)$rule['release_train_id'], null, ['progress'=>$progress,'actor_id'=>$actorId?:null]);
            return 'Progression cible validée et journalisée (sans écrasement des calculs métier).';
        }
        throw new RuntimeException('Action non prise en charge.');
    }

    private function writeLog(array $rule, string $uuid, bool $simulation, string $status, int $duration, string $message, array $context, int $actorId): void
    {
        $json = json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        $statement = Database::connection()->prepare(
            'INSERT INTO axiom_release_automation_logs '
            . '(organisation_id, release_train_id, rule_id, execution_uuid, trigger_event, action_type, simulation, status, '
            . 'duration_ms, message, context_json, executed_by, created_at) '
            . 'VALUES (:organisation_id, :train_id, :rule_id, :execution_uuid, :trigger_event, :action_type, :simulation, '
            . ':status, :duration_ms, :message, :context_json, :executed_by, NOW())'
        );
        $statement->execute(['organisation_id'=>(int)$rule['organisation_id'],'train_id'=>(int)$rule['release_train_id'],
            'rule_id'=>(int)$rule['id'],'execution_uuid'=>$uuid,'trigger_event'=>(string)$rule['trigger_event'],
            'action_type'=>(string)$rule['action_type'],'simulation'=>$simulation?1:0,'status'=>$status,'duration_ms'=>$duration,
            'message'=>mb_substr($message,0,1000),'context_json'=>is_string($json)?$json:null,'executed_by'=>$actorId?:null]);
    }

    private function uuidV4(): string
    {
        $bytes = random_bytes(16); $bytes[6] = chr((ord($bytes[6]) & 0x0f) | 0x40); $bytes[8] = chr((ord($bytes[8]) & 0x3f) | 0x80);
        $hex = bin2hex($bytes); return substr($hex,0,8).'-'.substr($hex,8,4).'-'.substr($hex,12,4).'-'.substr($hex,16,4).'-'.substr($hex,20);
    }
}
