<?php

declare(strict_types=1);

namespace Tests\Framework\Http;

use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\DI\Container;
use App\Framework\Http\HttpMiddleware;
use App\Framework\Http\MiddlewareContext;
use App\Framework\Http\MiddlewareDependencyResolver;
use App\Framework\Http\Next;
use App\Framework\Http\RequestStateManager;
use App\Framework\Logging\Logger;
use App\Framework\Reflection\ReflectionProvider;
use PHPUnit\Framework\TestCase;

final class MiddlewareDependencyResolverTest extends TestCase
{
    private MiddlewareDependencyResolver $resolver;

    private Container $container;

    private ReflectionProvider $reflectionProvider;

    private Logger $logger;

    protected function setUp(): void
    {
        $this->container = $this->createMock(Container::class);
        $this->reflectionProvider = $this->createMock(ReflectionProvider::class);
        $this->logger = $this->createMock(Logger::class);

        $this->resolver = new MiddlewareDependencyResolver(
            $this->reflectionProvider,
            $this->container,
            $this->logger
        );
    }

    public function test_resolves_simple_middleware_without_dependencies(): void
    {
        // Create a simple middleware class for testing
        $middlewareClass = SimpleTestMiddleware::class;

        // Mock that the class exists and is instantiable
        $this->reflectionProvider
            ->method('isInstantiable')
            ->willReturn(true);

        // Mock that it has no constructor parameters
        $this->reflectionProvider
            ->method('getMethodParameters')
            ->willReturn([]);

        // Mock that it implements HttpMiddleware
        $this->reflectionProvider
            ->method('implementsInterface')
            ->with(
                $this->callback(fn ($className) => $className instanceof ClassName),
                HttpMiddleware::class
            )
            ->willReturn(true);

        $result = $this->resolver->resolve([$middlewareClass]);

        $this->assertCount(1, $result->getMiddlewares());
        $this->assertContains($middlewareClass, $result->getMiddlewares());
    }

    public function test_filters_out_middleware_with_missing_dependencies(): void
    {
        $middlewareClass = MiddlewareWithDependencies::class;

        // Mock that the class exists and is instantiable
        $this->reflectionProvider
            ->method('isInstantiable')
            ->willReturn(true);

        // Mock constructor parameter that requires a dependency
        $parameterMock = $this->createMock(\ReflectionParameter::class);
        $typeMock = $this->createMock(\ReflectionType::class);

        $parameterMock->method('getType')->willReturn($typeMock);
        $parameterMock->method('isOptional')->willReturn(false);
        $parameterMock->method('allowsNull')->willReturn(false);

        $typeMock->method('isBuiltin')->willReturn(false);
        $typeMock->method('getName')->willReturn('SomeService');

        $this->reflectionProvider
            ->method('getMethodParameters')
            ->willReturn([$parameterMock]);

        // Mock that the dependency is NOT available in container
        $this->container
            ->method('has')
            ->with('SomeService')
            ->willReturn(false);

        // Mock that it implements HttpMiddleware
        $this->reflectionProvider
            ->method('implementsInterface')
            ->willReturn(true);

        // Expect warning to be logged
        $this->logger
            ->expects($this->once())
            ->method('warning')
            ->with($this->stringContains('Missing dependencies for MiddlewareWithDependencies: SomeService'));

        $result = $this->resolver->resolve([$middlewareClass]);

        // Should be filtered out due to missing dependency
        $this->assertCount(0, $result->getMiddlewares());
    }

    public function test_includes_middleware_with_available_dependencies(): void
    {
        $middlewareClass = MiddlewareWithDependencies::class;

        // Mock that the class exists and is instantiable
        $this->reflectionProvider
            ->method('isInstantiable')
            ->willReturn(true);

        // Mock constructor parameter that requires a dependency
        $parameterMock = $this->createMock(\ReflectionParameter::class);
        $typeMock = $this->createMock(\ReflectionType::class);

        $parameterMock->method('getType')->willReturn($typeMock);
        $parameterMock->method('isOptional')->willReturn(false);
        $parameterMock->method('allowsNull')->willReturn(false);

        $typeMock->method('isBuiltin')->willReturn(false);
        $typeMock->method('getName')->willReturn('SomeService');

        $this->reflectionProvider
            ->method('getMethodParameters')
            ->willReturn([$parameterMock]);

        // Mock that the dependency IS available in container
        $this->container
            ->method('has')
            ->with('SomeService')
            ->willReturn(true);

        // Mock that it implements HttpMiddleware
        $this->reflectionProvider
            ->method('implementsInterface')
            ->willReturn(true);

        $result = $this->resolver->resolve([$middlewareClass]);

        // Should be included because dependency is available
        $this->assertCount(1, $result->getMiddlewares());
        $this->assertContains($middlewareClass, $result->getMiddlewares());
    }

    public function test_logs_information_about_resolution_process(): void
    {
        $middlewareClass = SimpleTestMiddleware::class;

        // Mock successful resolution
        $this->reflectionProvider->method('isInstantiable')->willReturn(true);
        $this->reflectionProvider->method('getMethodParameters')->willReturn([]);
        $this->reflectionProvider->method('implementsInterface')->willReturn(true);

        // Expect debug and info logs
        $this->logger
            ->expects($this->once())
            ->method('debug')
            ->with($this->stringContains('Starting resolution for 1 middlewares'));

        $this->logger
            ->expects($this->once())
            ->method('info')
            ->with($this->stringContains('Resolution completed with 1 middlewares'));

        $this->resolver->resolve([$middlewareClass]);
    }

    public function test_handles_non_existent_middleware_class(): void
    {
        $nonExistentClass = 'NonExistentMiddleware';

        // Expect warning to be logged
        $this->logger
            ->expects($this->once())
            ->method('warning')
            ->with($this->stringContains('Class not found: NonExistentMiddleware'));

        $result = $this->resolver->resolve([$nonExistentClass]);

        // Should return empty result
        $this->assertCount(0, $result->getMiddlewares());
    }
}

// Test middleware classes
final class SimpleTestMiddleware implements HttpMiddleware
{
    public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
    {
        return $next($context);
    }
}

final class MiddlewareWithDependencies implements HttpMiddleware
{
    public function __construct(
        private object $someService
    ) {
    }

    public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
    {
        return $next($context);
    }
}
