<?php
declare(strict_types=1);

namespace App\Services;

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

final class ReleaseAutomationService
{
    private const TRIGGERS = ['step.created', 'step.updated', 'step.completed', 'milestone.completed', 'release.completed'];
    private const ACTIONS = ['audit_log', 'set_release_status', 'set_release_progress'];
    private const RELEASE_STATUSES = ['draft', 'planned', 'active', 'completed', 'cancelled'];

    public function listRules(int $organisationId, int $trainId = 0): array
    {
        if ($trainId > 0) {
            $this->requireTrain($trainId, $organisationId);
            $statement = Database::connection()->prepare(
                'SELECT r.*, t.code AS release_code, t.name AS release_name '
                . 'FROM axiom_release_automation_rules r '
                . 'INNER JOIN axiom_release_trains t ON t.id = r.release_train_id '
                . 'AND t.organisation_id = r.organisation_id AND t.archived_at IS NULL '
                . 'WHERE r.organisation_id = :organisation_id AND r.release_train_id = :train_id '
                . 'AND r.archived_at IS NULL ORDER BY r.priority ASC, r.id ASC'
            );
            $statement->execute(['organisation_id' => $organisationId, 'train_id' => $trainId]);
            return $statement->fetchAll(PDO::FETCH_ASSOC);
        }
        $statement = Database::connection()->prepare(
            'SELECT r.*, t.code AS release_code, t.name AS release_name '
            . 'FROM axiom_release_automation_rules r '
            . 'INNER JOIN axiom_release_trains t ON t.id = r.release_train_id '
            . 'AND t.organisation_id = r.organisation_id AND t.archived_at IS NULL '
            . 'WHERE r.organisation_id = :organisation_id AND r.archived_at IS NULL '
            . 'ORDER BY r.priority ASC, r.id ASC'
        );
        $statement->execute(['organisation_id' => $organisationId]);
        return $statement->fetchAll(PDO::FETCH_ASSOC);
    }

    public function listLogs(int $organisationId, int $limit = 50): array
    {
        $limit = max(1, min(100, $limit));
        $statement = Database::connection()->prepare(
            'SELECT l.*, r.name AS rule_name, t.code AS release_code '
            . 'FROM axiom_release_automation_logs l '
            . 'INNER JOIN axiom_release_automation_rules r ON r.id = l.rule_id AND r.organisation_id = l.organisation_id '
            . 'INNER JOIN axiom_release_trains t ON t.id = l.release_train_id AND t.organisation_id = l.organisation_id '
            . 'WHERE l.organisation_id = :organisation_id ORDER BY l.id DESC LIMIT ' . $limit
        );
        $statement->execute(['organisation_id' => $organisationId]);
        return $statement->fetchAll(PDO::FETCH_ASSOC);
    }

    public function find(int $id, int $organisationId): ?array
    {
        $statement = Database::connection()->prepare(
            'SELECT * FROM axiom_release_automation_rules '
            . 'WHERE id = :id AND organisation_id = :organisation_id AND archived_at IS NULL'
        );
        $statement->execute(['id' => $id, 'organisation_id' => $organisationId]);
        $row = $statement->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    }

    public function trains(int $organisationId): array
    {
        $statement = Database::connection()->prepare(
            'SELECT id, code, name FROM axiom_release_trains '
            . 'WHERE organisation_id = :organisation_id AND archived_at IS NULL '
            . 'ORDER BY code ASC, id DESC'
        );
        $statement->execute(['organisation_id' => $organisationId]);
        return $statement->fetchAll(PDO::FETCH_ASSOC);
    }

    public function saveRule(array $input, ?int $id, int $actorId, int $organisationId): int
    {
        $trainId = max(1, (int)($input['release_train_id'] ?? 0));
        $this->requireTrain($trainId, $organisationId);
        $name = trim((string)($input['name'] ?? ''));
        $description = trim((string)($input['description'] ?? ''));
        $trigger = trim((string)($input['trigger_event'] ?? ''));
        $action = trim((string)($input['action_type'] ?? ''));
        $priority = max(1, min(9999, (int)($input['priority'] ?? 100)));
        $parameters = $this->validateParameters((string)($input['parameters_json'] ?? '{}'));
        if ($name === '' || mb_strlen($name) > 180) throw new RuntimeException('Nom obligatoire (180 caractères maximum).');
        if (mb_strlen($description) > 5000) throw new RuntimeException('Description trop longue.');
        if (!in_array($trigger, self::TRIGGERS, true)) throw new RuntimeException('Déclencheur invalide.');
        if (!in_array($action, self::ACTIONS, true)) throw new RuntimeException('Action invalide.');
        $json = json_encode($parameters, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if (!is_string($json)) throw new RuntimeException('Paramètres JSON invalides.');
        $pdo = Database::connection();
        $pdo->beginTransaction();
        try {
            if ($id !== null) {
                $before = $this->find($id, $organisationId);
                if ($before === null) throw new RuntimeException('Règle introuvable.');
                $statement = $pdo->prepare(
                    'UPDATE axiom_release_automation_rules SET release_train_id = :train_id, name = :name, '
                    . 'description = :description, trigger_event = :trigger_event, action_type = :action_type, '
                    . 'parameters_json = :parameters_json, priority = :priority, updated_by = :actor_id, updated_at = NOW() '
                    . 'WHERE id = :id AND organisation_id = :organisation_id AND archived_at IS NULL'
                );
                $statement->execute(['train_id'=>$trainId,'name'=>$name,'description'=>$description,'trigger_event'=>$trigger,
                    'action_type'=>$action,'parameters_json'=>$json,'priority'=>$priority,'actor_id'=>$actorId?:null,
                    'id'=>$id,'organisation_id'=>$organisationId]);
                $this->audit('release_automation.rule_updated', $id, $before, ['name'=>$name,'trigger'=>$trigger,'action'=>$action]);
                $pdo->commit();
                return $id;
            }
            $statement = $pdo->prepare(
                'INSERT INTO axiom_release_automation_rules '
                . '(organisation_id, release_train_id, name, description, trigger_event, action_type, parameters_json, '
                . 'is_active, priority, created_by, updated_by, created_at, updated_at) '
                . 'VALUES (:organisation_id, :train_id, :name, :description, :trigger_event, :action_type, '
                . ':parameters_json, 1, :priority, :actor_id, :actor_id, NOW(), NOW())'
            );
            $statement->execute(['organisation_id'=>$organisationId,'train_id'=>$trainId,'name'=>$name,'description'=>$description,
                'trigger_event'=>$trigger,'action_type'=>$action,'parameters_json'=>$json,'priority'=>$priority,'actor_id'=>$actorId?:null]);
            $newId = (int)$pdo->lastInsertId();
            $this->audit('release_automation.rule_created', $newId, null, ['name'=>$name,'trigger'=>$trigger,'action'=>$action]);
            $pdo->commit();
            return $newId;
        } catch (Throwable $exception) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            throw $exception;
        }
    }

    public function setActive(int $id, bool $active, int $actorId, int $organisationId): void
    {
        $rule = $this->requireRule($id, $organisationId);
        $statement = Database::connection()->prepare(
            'UPDATE axiom_release_automation_rules SET is_active = :is_active, updated_by = :actor_id, updated_at = NOW() '
            . 'WHERE id = :id AND organisation_id = :organisation_id AND archived_at IS NULL'
        );
        $statement->execute(['is_active'=>$active?1:0,'actor_id'=>$actorId?:null,'id'=>$id,'organisation_id'=>$organisationId]);
        $this->audit('release_automation.rule_status_changed', $id, ['is_active'=>(int)$rule['is_active']], ['is_active'=>$active]);
    }

    public function archive(int $id, int $actorId, int $organisationId): void
    {
        $this->requireRule($id, $organisationId);
        $statement = Database::connection()->prepare(
            'UPDATE axiom_release_automation_rules SET archived_at = NOW(), is_active = 0, updated_by = :actor_id, updated_at = NOW() '
            . 'WHERE id = :id AND organisation_id = :organisation_id AND archived_at IS NULL'
        );
        $statement->execute(['actor_id'=>$actorId?:null,'id'=>$id,'organisation_id'=>$organisationId]);
        $this->audit('release_automation.rule_archived', $id, null, ['archived'=>true]);
    }

    public function simulate(int $id, int $actorId, int $organisationId): array
    {
        $rule = $this->requireRule($id, $organisationId);
        return (new ReleaseAutomationEngine())->executeRule($rule, ['source'=>'manual_simulation'], $actorId, true);
    }

    private function validateParameters(string $raw): array
    {
        $raw = trim($raw);
        if ($raw === '') return [];
        $decoded = json_decode($raw, true, 16, JSON_THROW_ON_ERROR);
        if (!is_array($decoded)) throw new RuntimeException('Les paramètres doivent être un objet JSON.');
        return $decoded;
    }

    private function requireRule(int $id, int $organisationId): array
    {
        $rule = $this->find($id, $organisationId);
        if ($rule === null) throw new RuntimeException('Règle introuvable.');
        return $rule;
    }

    private function requireTrain(int $id, int $organisationId): void
    {
        $statement = Database::connection()->prepare(
            'SELECT id FROM axiom_release_trains WHERE id = :id AND organisation_id = :organisation_id AND archived_at IS NULL'
        );
        $statement->execute(['id'=>$id,'organisation_id'=>$organisationId]);
        if (!$statement->fetchColumn()) throw new RuntimeException('Release train introuvable.');
    }

    private function audit(string $event, int $id, ?array $before, ?array $after): void
    {
        try { (new AuditLogger())->record($event, 'release_automation', 'automation_rule', $id, $before, $after); } catch (Throwable) {}
    }
}
