<?php

declare(strict_types=1);

namespace App\Framework\EventSourcing\Sagas;

use App\Framework\CommandBus\CommandBus;
use App\Framework\EventSourcing\DomainEvent;

/**
 * Saga Manager
 *
 * Orchestrates saga execution and coordinates with command bus
 */
final readonly class SagaManager
{
    public function __construct(
        private SagaRepository $repository,
        private SagaRegistry $registry,
        private CommandBus $commandBus
    ) {
    }

    /**
     * Start a new saga
     */
    public function startSaga(string $sagaName, array $initialData = []): SagaId
    {
        $sagaClass = $this->registry->getSagaClass($sagaName);
        $sagaId = SagaId::generate();

        $state = SagaState::start($sagaId, $sagaName, $initialData);
        $saga = new $sagaClass($state);

        $this->repository->save($saga);

        return $sagaId;
    }

    /**
     * Handle event and route to interested sagas
     */
    public function handleEvent(DomainEvent $event): void
    {
        // Get saga classes interested in this event
        $interestedSagas = $this->registry->getSagasForEvent($event);

        foreach ($interestedSagas as $sagaClass) {
            // Find or create saga instances
            $sagas = $this->repository->findBySagaClass($sagaClass);

            if (empty($sagas)) {
                // Auto-start saga if configured
                if ($this->registry->shouldAutoStart($sagaClass, $event)) {
                    $sagaId = $this->startSaga($sagaClass::getName());
                    $sagas = [$this->repository->find($sagaId)];
                }
            }

            // Let each saga handle the event
            foreach ($sagas as $saga) {
                if ($saga->isCompleted()) {
                    continue; // Skip completed sagas
                }

                try {
                    // Handle event and get resulting commands
                    $commands = $saga->handle($event);

                    // Execute commands via command bus
                    foreach ($commands as $command) {
                        $this->commandBus->dispatch($command);
                    }

                    // Save updated saga state
                    $this->repository->save($saga);
                } catch (\Exception $e) {
                    // Saga failed - initiate compensation
                    $this->compensateSaga($saga, $e->getMessage());
                }
            }
        }
    }

    /**
     * Compensate (rollback) a failed saga
     */
    public function compensateSaga(SagaInterface $saga, string $reason): void
    {
        try {
            // Get compensation commands
            $compensationCommands = $saga->compensate($reason);

            // Execute compensation commands
            foreach ($compensationCommands as $command) {
                $this->commandBus->dispatch($command);
            }

            // Save compensated saga state
            $this->repository->save($saga);
        } catch (\Exception $e) {
            // Compensation failed - log and alert
            // In production: trigger alerts, manual intervention
            error_log("Saga compensation failed: {$e->getMessage()}");
        }
    }

    /**
     * Get saga by ID
     */
    public function getSaga(SagaId $sagaId): ?SagaInterface
    {
        return $this->repository->find($sagaId);
    }

    /**
     * Get all running sagas
     */
    public function getRunningSagas(): array
    {
        return $this->repository->findByStatus(SagaStatus::RUNNING);
    }

    /**
     * Get saga state
     */
    public function getSagaState(SagaId $sagaId): ?SagaState
    {
        $saga = $this->repository->find($sagaId);

        return $saga?->getState();
    }

    /**
     * Complete saga manually
     */
    public function completeSaga(SagaId $sagaId): void
    {
        $saga = $this->repository->find($sagaId);

        if (! $saga) {
            throw new \RuntimeException("Saga not found: {$sagaId->toString()}");
        }

        if ($saga->isCompleted()) {
            return; // Already completed
        }

        // Force completion
        $state = $saga->getState()->withCompleted();
        $completedSaga = new ($saga::class)($state);

        $this->repository->save($completedSaga);
    }
}
