<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Event\Sourcing\Aggregate;

use PpPhp\Core\Platform\Event\Sourcing\EventStore\EventStoreRuntime;

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

    private AggregateRepository $repository;
    private AggregateRepositoryStore $store;

    private function __construct(private readonly string $basePath)
    {
        $this->repository = new AggregateRepository(EventStoreRuntime::instance($basePath));
        $this->store = new AggregateRepositoryStore($basePath);
    }

    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 save(string $aggregateId, string $aggregateType, string $eventType, array $payload = [], array $metadata = []): AggregateState
    {
        $state = $this->repository->save($aggregateId, $aggregateType, $eventType, $payload, $metadata);
        $this->saveSnapshot([$state]);

        return $state;
    }

    public function load(string $aggregateId, string $aggregateType = ''): AggregateState
    {
        return $this->repository->load($aggregateId, $aggregateType);
    }

    public function history(string $aggregateId): array
    {
        return $this->repository->history($aggregateId);
    }

    public function exists(string $aggregateId): bool
    {
        return $this->repository->exists($aggregateId);
    }

    public function saveSnapshot(array $states): AggregateRepositorySnapshot
    {
        $aggregates = array_map(static fn (AggregateState $state): array => $state->toArray(), $states);
        return $this->store->save(new AggregateRepositorySnapshot(
            $aggregates,
            AggregateRepositoryStatistics::from($aggregates),
            date('c')
        ));
    }

    public function clear(): void
    {
        $this->store->reset();
    }

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

    public function summary(array $aggregateIds = []): array
    {
        $aggregates = [];

        foreach ($aggregateIds as $aggregateId) {
            $aggregates[] = $this->load((string) $aggregateId)->toArray();
        }

        if ($aggregates === [] && $this->store->exists()) {
            $snapshot = $this->store->load();
            $aggregates = $snapshot->aggregates();
        }

        return [
            'aggregates' => $aggregates,
            'statistics' => AggregateRepositoryStatistics::from($aggregates),
            'persisted' => $this->store->exists(),
            'stateFile' => $this->store->stateFile(),
        ];
    }

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