<?php

declare(strict_types=1);

namespace App\Framework\Http;

use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;

/**
 * Exception thrown when middleware dependency resolution fails
 */
final class MiddlewareDependencyException extends FrameworkException
{
    /**
     * @param array<ClassName> $missingDependencies
     * @param array<string, mixed> $middlewareContext
     */
    public function __construct(
        string $message,
        private readonly ClassName $middlewareClass,
        private readonly array $missingDependencies = [],
        private readonly array $middlewareContext = []
    ) {
        $exceptionContext = ExceptionContext::forOperation('middleware.dependency', 'http')
            ->withData([
                'middleware_class' => $middlewareClass->toString(),
                'missing_dependencies' => array_map(fn ($dep) => $dep->toString(), $missingDependencies),
                'context' => $this->middlewareContext,
            ]);

        parent::__construct($message, $exceptionContext);
    }

    public function getMiddlewareClass(): ClassName
    {
        return $this->middlewareClass;
    }

    /**
     * @return array<ClassName>
     */
    public function getMissingDependencies(): array
    {
        return $this->missingDependencies;
    }

    /**
     * @return array<string, mixed>
     */
    public function getMiddlewareContext(): array
    {
        return $this->middlewareContext;
    }

    /**
     * @param array<ClassName> $missingDeps
     */
    public static function missingDependencies(ClassName $middlewareClass, array $missingDeps): self
    {
        $depNames = array_map(fn (ClassName $dep) => $dep->getShortName(), $missingDeps);

        $message = sprintf(
            'Middleware %s has missing dependencies: %s',
            $middlewareClass->getShortName(),
            implode(', ', $depNames)
        );

        return new self($message, $middlewareClass, $missingDeps);
    }

    public static function cannotInstantiate(ClassName $middlewareClass, string $reason): self
    {
        $message = sprintf(
            'Cannot instantiate middleware %s: %s',
            $middlewareClass->getShortName(),
            $reason
        );

        return new self($message, $middlewareClass, [], ['reason' => $reason]);
    }

    /**
     * @param array<ClassName> $cycle
     */
    public static function circularDependency(ClassName $middlewareClass, array $cycle): self
    {
        $cycleNames = array_map(fn (ClassName $class) => $class->getShortName(), $cycle);

        $message = sprintf(
            'Circular dependency detected for middleware %s: %s',
            $middlewareClass->getShortName(),
            implode(' → ', $cycleNames)
        );

        return new self($message, $middlewareClass, [], ['cycle' => $cycle]);
    }

    public static function classNotFound(ClassName $middlewareClass): self
    {
        $message = sprintf(
            'Middleware class %s not found (Namespace: %s)',
            $middlewareClass->getShortName(),
            $middlewareClass->getNamespace() ?: 'global'
        );

        return new self($message, $middlewareClass);
    }

    public static function interfaceNotImplemented(ClassName $middlewareClass, ClassName $requiredInterface): self
    {
        $message = sprintf(
            'Middleware %s does not implement required interface %s',
            $middlewareClass->getShortName(),
            $requiredInterface->getShortName()
        );

        return new self($message, $middlewareClass, [], ['required_interface' => $requiredInterface]);
    }
}
