<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Workflow\Compensation;

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

    /** @var array<string, WorkflowCompensationPlan> */
    private array $plans = [];
    /** @var WorkflowRollbackRequest[] */
    private array $requests = [];
    /** @var WorkflowCompensationResult[] */
    private array $results = [];
    private WorkflowCompensationStore $store;

    private function __construct(private readonly string $basePath)
    {
        $this->store = new WorkflowCompensationStore($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 registerPlan(WorkflowCompensationPlan $plan): WorkflowCompensationPlan
    {
        $this->plans[$plan->workflowId()] = $plan;
        $this->save();
        return $plan;
    }

    public function rollback(WorkflowRollbackRequest $request): int
    {
        $this->requests[] = $request;

        $plan = $this->plans[$request->workflowId()] ?? null;
        if ($plan === null || !$plan->active()) {
            $this->results[] = WorkflowCompensationResult::failed($request, '', 'compensation plan not found');
            $this->save();
            return 0;
        }

        $executed = 0;
        foreach ($plan->actionsForCompleted($request->completedActivityIds()) as $action) {
            $this->results[] = WorkflowCompensationResult::success($request, $action);
            $executed++;
        }

        $this->save();
        return $executed;
    }

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

        $this->plans = [];
        foreach ($snapshot->plans() as $plan) {
            if (is_array($plan)) {
                $p = WorkflowCompensationPlan::fromArray($plan);
                $this->plans[$p->workflowId()] = $p;
            }
        }

        $this->requests = [];
        foreach ($snapshot->requests() as $request) {
            if (is_array($request)) {
                $this->requests[] = WorkflowRollbackRequest::fromArray($request);
            }
        }

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

    public function snapshot(): WorkflowCompensationSnapshot
    {
        $plans = array_map(static fn (WorkflowCompensationPlan $plan): array => $plan->toArray(), array_values($this->plans));
        $requests = array_map(static fn (WorkflowRollbackRequest $request): array => $request->toArray(), $this->requests);
        $results = array_map(static fn (WorkflowCompensationResult $result): array => $result->toArray(), $this->results);

        return new WorkflowCompensationSnapshot(
            $plans,
            $requests,
            $results,
            WorkflowCompensationStatistics::from($plans, $requests, $results),
            date('c')
        );
    }

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

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

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

    public function summary(): array
    {
        $snapshot = $this->snapshot();

        return [
            'plans' => $snapshot->plans(),
            'requests' => $snapshot->requests(),
            'results' => $snapshot->results(),
            'statistics' => $snapshot->statistics(),
            'persisted' => $this->store->exists(),
            'stateFile' => $this->store->stateFile(),
        ];
    }

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