<?php

declare(strict_types=1);

use App\Framework\Auth\RouteAuthorizationService;
use App\Framework\Auth\ValueObjects\NamespaceAccessPolicy;
use App\Framework\Config\TypedConfiguration;
use App\Framework\DI\DefaultContainer;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\IpAddress;
use App\Framework\Http\Method;
use App\Framework\Router\Exception\RouteNotFound;
use App\Framework\Router\RouteContext;

describe('Route Authorization Integration', function () {
    beforeEach(function () {
        $this->container = new DefaultContainer();

        // Setup stub config
        $this->config = createStubConfig(debug: false);
    });

    afterEach(function () {
        Mockery::close();
    });

    it('integrates namespace blocking with IP restrictions', function () {
        // Real-world scenario: Admin area with IP restrictions AND namespace blocking
        $namespaceConfig = [
            'App\Application\Admin\*' => [
                'visibility' => 'admin',  // IP-restricted to admin IPs
                'access_policy' => NamespaceAccessPolicy::blockedExcept(
                    'App\Application\Admin\LoginController',
                    'App\Application\Admin\HealthController'
                ),
            ],
        ];

        $service = new RouteAuthorizationService($this->config, $namespaceConfig);

        // Test 1: Login should be accessible from any IP (in allowlist)
        $loginRequest = createHttpRequest('192.168.1.100', '/admin/login');
        $loginRoute = createRouteContext('App\Application\Admin\LoginController', '/admin/login');

        expect(fn () => $service->authorize($loginRequest, $loginRoute))
            ->not->toThrow(RouteNotFound::class);

        // Test 2: Dashboard blocked (not in allowlist)
        $dashboardRequest = createHttpRequest('192.168.1.100', '/admin/dashboard');
        $dashboardRoute = createRouteContext('App\Application\Admin\Dashboard', '/admin/dashboard');

        expect(fn () => $service->authorize($dashboardRequest, $dashboardRoute))
            ->toThrow(RouteNotFound::class);

        // Test 3: Health controller allowed (in allowlist)
        $healthRequest = createHttpRequest('10.0.0.5', '/admin/health');
        $healthRoute = createRouteContext('App\Application\Admin\HealthController', '/admin/health');

        expect(fn () => $service->authorize($healthRequest, $healthRoute))
            ->not->toThrow(RouteNotFound::class);
    });

    it('handles multiple namespace configurations independently', function () {
        $namespaceConfig = [
            // Admin: Completely blocked
            'App\Application\Admin\*' => [
                'access_policy' => NamespaceAccessPolicy::blocked(),
            ],
            // API: Blocked except health endpoint
            'App\Application\Api\*' => [
                'access_policy' => NamespaceAccessPolicy::blockedExcept(
                    'App\Application\Api\HealthController'
                ),
            ],
            // Web: No restrictions
            'App\Application\Web\*' => [
                'visibility' => 'public',
            ],
        ];

        $service = new RouteAuthorizationService($this->config, $namespaceConfig);

        $request = createHttpRequest('127.0.0.1', '/test');

        // Admin: All blocked
        $adminRoute = createRouteContext('App\Application\Admin\Dashboard', '/admin/dashboard');
        expect(fn () => $service->authorize($request, $adminRoute))
            ->toThrow(RouteNotFound::class);

        // API: Health allowed
        $apiHealthRoute = createRouteContext('App\Application\Api\HealthController', '/api/health');
        expect(fn () => $service->authorize($request, $apiHealthRoute))
            ->not->toThrow(RouteNotFound::class);

        // API: Users blocked
        $apiUsersRoute = createRouteContext('App\Application\Api\UsersController', '/api/users');
        expect(fn () => $service->authorize($request, $apiUsersRoute))
            ->toThrow(RouteNotFound::class);

        // Web: Allowed
        $webRoute = createRouteContext('App\Application\Web\HomeController', '/');
        expect(fn () => $service->authorize($request, $webRoute))
            ->not->toThrow(RouteNotFound::class);
    });

    it('production-like configuration scenario', function () {
        // Realistic production setup
        $namespaceConfig = [
            // Internal tools - completely locked down
            'App\Application\Internal\*' => [
                'visibility' => 'admin',
                'access_policy' => NamespaceAccessPolicy::blocked(),
            ],

            // Admin - IP restricted, only public endpoints accessible
            'App\Application\Admin\*' => [
                'visibility' => 'admin',
                'access_policy' => NamespaceAccessPolicy::blockedExcept(
                    'App\Application\Admin\LoginController'
                ),
            ],

            // API - public but monitored
            'App\Application\Api\*' => [
                'visibility' => 'public',
                // No access_policy - all API routes accessible
            ],
        ];

        $service = new RouteAuthorizationService($this->config, $namespaceConfig);

        // Public user accessing different areas
        $publicRequest = createHttpRequest('93.184.216.34', '/test');

        // Internal: Blocked (even from public IP, namespace blocked + IP restricted)
        $internalRoute = createRouteContext('App\Application\Internal\ToolsController', '/internal/tools');
        expect(fn () => $service->authorize($publicRequest, $internalRoute))
            ->toThrow(RouteNotFound::class);

        // Admin Login: Allowed (in allowlist, IP restriction doesn't apply to allowlist)
        $adminLoginRoute = createRouteContext('App\Application\Admin\LoginController', '/admin/login');
        expect(fn () => $service->authorize($publicRequest, $adminLoginRoute))
            ->not->toThrow(RouteNotFound::class);

        // Admin Dashboard: Blocked (not in allowlist)
        $adminDashboardRoute = createRouteContext('App\Application\Admin\Dashboard', '/admin/dashboard');
        expect(fn () => $service->authorize($publicRequest, $adminDashboardRoute))
            ->toThrow(RouteNotFound::class);

        // API: Allowed (public visibility, no namespace blocking)
        $apiRoute = createRouteContext('App\Application\Api\UsersController', '/api/users');
        expect(fn () => $service->authorize($publicRequest, $apiRoute))
            ->not->toThrow(RouteNotFound::class);
    });

    it('handles deeply nested namespaces correctly', function () {
        $namespaceConfig = [
            'App\Application\Admin\*' => [
                'access_policy' => NamespaceAccessPolicy::blockedExcept(
                    'App\Application\Admin\Auth\LoginController'
                ),
            ],
        ];

        $service = new RouteAuthorizationService($this->config, $namespaceConfig);

        $request = createHttpRequest('127.0.0.1', '/test');

        // Deeply nested allowed controller
        $loginRoute = createRouteContext(
            'App\Application\Admin\Auth\LoginController',
            '/admin/auth/login'
        );
        expect(fn () => $service->authorize($request, $loginRoute))
            ->not->toThrow(RouteNotFound::class);

        // Deeply nested blocked controller
        $userRoute = createRouteContext(
            'App\Application\Admin\Users\Management\UserController',
            '/admin/users/management'
        );
        expect(fn () => $service->authorize($request, $userRoute))
            ->toThrow(RouteNotFound::class);
    });

    it('handles no namespace configuration gracefully', function () {
        // Service with empty config should allow everything
        $service = new RouteAuthorizationService($this->config, []);

        $request = createHttpRequest('127.0.0.1', '/test');

        $routes = [
            createRouteContext('App\Application\Admin\Dashboard', '/admin/dashboard'),
            createRouteContext('App\Application\Api\UsersController', '/api/users'),
            createRouteContext('App\Application\Web\HomeController', '/'),
        ];

        foreach ($routes as $route) {
            expect(fn () => $service->authorize($request, $route))
                ->not->toThrow(RouteNotFound::class);
        }
    });
});

// Helper functions

function createStubConfig(bool $debug = false): TypedConfiguration
{
    return new class ($debug) extends TypedConfiguration {
        public function __construct(bool $debug)
        {
            $this->app = (object)['debug' => $debug];
        }
    };
}

function createHttpRequest(string $ipAddress, string $path): HttpRequest
{
    $server = Mockery::mock('server');
    $server->shouldReceive('getClientIp')
        ->andReturn(IpAddress::fromString($ipAddress));

    $request = Mockery::mock(HttpRequest::class);
    $request->server = $server;
    $request->path = $path;
    $request->method = Method::GET;

    return $request;
}

function createRouteContext(string $controllerClass, string $path): RouteContext
{
    $route = (object)[
        'controller' => $controllerClass,
        'action' => 'index',
        'attributes' => [],
    ];

    $match = (object)['route' => $route];

    $routeContext = Mockery::mock(RouteContext::class);
    $routeContext->match = $match;
    $routeContext->path = $path;
    $routeContext->shouldReceive('isSuccess')->andReturn(true);

    return $routeContext;
}
