<?php

declare(strict_types=1);

namespace App\Framework\EventSourcing\Sagas;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Duration;

/**
 * Cache-based Saga Repository
 *
 * Stores saga instances in SmartCache for fast access
 */
final readonly class CacheSagaRepository implements SagaRepository
{
    public function __construct(
        private Cache $cache
    ) {
    }

    public function save(SagaInterface $saga): void
    {
        $cacheKey = $this->getCacheKey($saga->getSagaId());

        $cacheItem = CacheItem::forSetting(
            key: $cacheKey,
            value: $saga,
            ttl: Duration::fromDays(7) // Keep sagas for 7 days
        );

        $this->cache->set($cacheItem);

        // Also index by saga class and status for queries
        $this->indexSaga($saga);
    }

    public function find(SagaId $sagaId): ?SagaInterface
    {
        $cacheKey = $this->getCacheKey($sagaId);
        $cacheItem = $this->cache->get($cacheKey);

        if ($cacheItem && $cacheItem->value instanceof SagaInterface) {
            return $cacheItem->value;
        }

        return null;
    }

    public function findBySagaClass(string $sagaClass): array
    {
        $indexKey = CacheKey::fromString("saga_index:class:{$sagaClass}");
        $cacheItem = $this->cache->get($indexKey);

        if (! $cacheItem) {
            return [];
        }

        $sagaIds = $cacheItem->value;
        $sagas = [];

        foreach ($sagaIds as $sagaId) {
            $saga = $this->find(SagaId::fromString($sagaId));
            if ($saga) {
                $sagas[] = $saga;
            }
        }

        return $sagas;
    }

    public function findByStatus(SagaStatus $status): array
    {
        $indexKey = CacheKey::fromString("saga_index:status:{$status->value}");
        $cacheItem = $this->cache->get($indexKey);

        if (! $cacheItem) {
            return [];
        }

        $sagaIds = $cacheItem->value;
        $sagas = [];

        foreach ($sagaIds as $sagaId) {
            $saga = $this->find(SagaId::fromString($sagaId));
            if ($saga && $saga->getState()->status === $status) {
                $sagas[] = $saga;
            }
        }

        return $sagas;
    }

    public function delete(SagaId $sagaId): void
    {
        $saga = $this->find($sagaId);

        if ($saga) {
            // Remove from indexes
            $this->removeFromIndex($saga);
        }

        $cacheKey = $this->getCacheKey($sagaId);
        $this->cache->forget($cacheKey);
    }

    public function findAll(): array
    {
        $indexKey = CacheKey::fromString('saga_index:all');
        $cacheItem = $this->cache->get($indexKey);

        if (! $cacheItem) {
            return [];
        }

        $sagaIds = $cacheItem->value;
        $sagas = [];

        foreach ($sagaIds as $sagaId) {
            $saga = $this->find(SagaId::fromString($sagaId));
            if ($saga) {
                $sagas[] = $saga;
            }
        }

        return $sagas;
    }

    private function getCacheKey(SagaId $sagaId): CacheKey
    {
        return CacheKey::fromString("saga:{$sagaId->toString()}");
    }

    private function indexSaga(SagaInterface $saga): void
    {
        $sagaId = $saga->getSagaId()->toString();
        $sagaClass = get_class($saga);
        $status = $saga->getState()->status;

        // Index by class
        $this->addToIndex("saga_index:class:{$sagaClass}", $sagaId);

        // Index by status
        $this->addToIndex("saga_index:status:{$status->value}", $sagaId);

        // Index all sagas
        $this->addToIndex('saga_index:all', $sagaId);
    }

    private function removeFromIndex(SagaInterface $saga): void
    {
        $sagaId = $saga->getSagaId()->toString();
        $sagaClass = get_class($saga);
        $status = $saga->getState()->status;

        $this->removeFromIndexKey("saga_index:class:{$sagaClass}", $sagaId);
        $this->removeFromIndexKey("saga_index:status:{$status->value}", $sagaId);
        $this->removeFromIndexKey('saga_index:all', $sagaId);
    }

    private function addToIndex(string $indexKey, string $sagaId): void
    {
        $cacheKey = CacheKey::fromString($indexKey);
        $cacheItem = $this->cache->get($cacheKey);

        $sagaIds = $cacheItem?->value ?? [];
        if (! in_array($sagaId, $sagaIds, true)) {
            $sagaIds[] = $sagaId;
        }

        $this->cache->set(CacheItem::forSetting(
            key: $cacheKey,
            value: $sagaIds,
            ttl: Duration::fromDays(7)
        ));
    }

    private function removeFromIndexKey(string $indexKey, string $sagaId): void
    {
        $cacheKey = CacheKey::fromString($indexKey);
        $cacheItem = $this->cache->get($cacheKey);

        if (! $cacheItem) {
            return;
        }

        $sagaIds = $cacheItem->value;
        $sagaIds = array_filter($sagaIds, fn ($id) => $id !== $sagaId);

        $this->cache->set(CacheItem::forSetting(
            key: $cacheKey,
            value: array_values($sagaIds),
            ttl: Duration::fromDays(7)
        ));
    }
}
