<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Rules;

final class RulesRuntime
{
    private static ?self $instance = null;

    /** @var array<string, RuleSet> */
    private array $ruleSets = [];
    /** @var array<string, DecisionTable> */
    private array $decisionTables = [];
    /** @var RuleEvaluationResult[] */
    private array $results = [];
    private RulesRuntimeStore $store;

    private function __construct(private readonly string $basePath)
    {
        $this->store = new RulesRuntimeStore($basePath);
        $this->hydrate();
    }

    public static function instance(?string $basePath = null): self
    {
        if (self::$instance === null) {
            $basePath ??= self::detectBasePath();
            self::$instance = new self($basePath);
        }

        return self::$instance;
    }

    public static function resetInstance(?string $basePath = null): self
    {
        $basePath ??= self::detectBasePath();
        self::$instance = new self($basePath);

        return self::$instance;
    }

    public function registerRuleSet(RuleSet $ruleSet): RuleSet
    {
        $this->ruleSets[$ruleSet->ruleSetId()] = $ruleSet;
        $this->save();
        return $ruleSet;
    }

    public function registerDecisionTable(DecisionTable $table): DecisionTable
    {
        $this->decisionTables[$table->tableId()] = $table;
        $this->save();
        return $table;
    }

    public function evaluate(string $ruleSetId, array $facts): RuleEvaluationResult
    {
        $ruleSet = $this->ruleSets[$ruleSetId] ?? null;
        if ($ruleSet === null || !$ruleSet->active()) {
            $result = RuleEvaluationResult::create($ruleSetId, [], []);
            $this->results[] = $result;
            $this->save();
            return $result;
        }

        $matchedRules = [];
        $actions = [];

        foreach ($ruleSet->rules() as $rule) {
            if (!$rule->matches($facts)) {
                continue;
            }

            $matchedRules[] = $rule->ruleId();
            foreach ($rule->actions() as $action) {
                $actions[] = $action->toArray();
            }
        }

        $result = RuleEvaluationResult::create($ruleSetId, $matchedRules, $actions);
        $this->results[] = $result;
        $this->save();

        return $result;
    }

    public function decide(string $tableId, array $facts): RuleEvaluationResult
    {
        $table = $this->decisionTables[$tableId] ?? null;
        $outputs = $table ? $table->evaluate($facts) : [];

        $result = RuleEvaluationResult::create('decision:' . $tableId, [], [], $outputs);
        $this->results[] = $result;
        $this->save();

        return $result;
    }

    public function hydrate(): void
    {
        $snapshot = $this->store->load();

        $this->ruleSets = [];
        foreach ($snapshot->ruleSets() as $ruleSet) {
            if (is_array($ruleSet)) {
                $r = RuleSet::fromArray($ruleSet);
                $this->ruleSets[$r->ruleSetId()] = $r;
            }
        }

        $this->decisionTables = [];
        foreach ($snapshot->decisionTables() as $table) {
            if (is_array($table)) {
                $t = DecisionTable::fromArray($table);
                $this->decisionTables[$t->tableId()] = $t;
            }
        }

        $this->results = [];
        foreach ($snapshot->results() as $result) {
            if (is_array($result)) {
                $this->results[] = RuleEvaluationResult::fromArray($result);
            }
        }
    }

    public function snapshot(): RulesRuntimeSnapshot
    {
        $ruleSets = array_map(static fn (RuleSet $ruleSet): array => $ruleSet->toArray(), array_values($this->ruleSets));
        $decisionTables = array_map(static fn (DecisionTable $table): array => $table->toArray(), array_values($this->decisionTables));
        $results = array_map(static fn (RuleEvaluationResult $result): array => $result->toArray(), $this->results);

        return new RulesRuntimeSnapshot($ruleSets, $decisionTables, $results, RulesStatistics::from($ruleSets, $decisionTables, $results), date('c'));
    }

    public function save(): RulesRuntimeSnapshot
    {
        return $this->store->save($this->snapshot());
    }

    public function clear(): void
    {
        $this->ruleSets = [];
        $this->decisionTables = [];
        $this->results = [];
        $this->store->reset();
    }

    public function store(): RulesRuntimeStore { return $this->store; }

    public function summary(): array
    {
        $snapshot = $this->snapshot();
        return [
            'ruleSets' => $snapshot->ruleSets(),
            'decisionTables' => $snapshot->decisionTables(),
            'results' => $snapshot->results(),
            'statistics' => $snapshot->statistics(),
            'persisted' => $this->store->exists(),
            'stateFile' => $this->store->stateFile(),
        ];
    }

    private static function detectBasePath(): string
    {
        $candidate = dirname(__DIR__, 3);
        $real = realpath($candidate);
        return $real !== false ? $real : $candidate;
    }
}
