<?php

declare(strict_types=1);

namespace Tests\Unit\Framework\Http;

use App\Framework\Http\HttpMiddleware;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\Method;
use App\Framework\Http\MiddlewareContext;
use App\Framework\Http\MiddlewareManager;
use App\Framework\Http\Next;
use App\Framework\Http\Request;
use App\Framework\Http\RequestStateManager;
use App\Framework\Http\Status;

test('middleware pipeline executes in order', function () {
    $executionOrder = [];

    $middleware1 = new class ($executionOrder) implements HttpMiddleware {
        public function __construct(private array &$order)
        {
        }

        public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
        {
            $this->order[] = 'before-1';
            $context = $next($context);
            $this->order[] = 'after-1';

            return $context;
        }
    };

    $middleware2 = new class ($executionOrder) implements HttpMiddleware {
        public function __construct(private array &$order)
        {
        }

        public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
        {
            $this->order[] = 'before-2';
            $context = $next($context);
            $this->order[] = 'after-2';

            return $context;
        }
    };

    $manager = new MiddlewareManager();
    $manager->addMiddleware($middleware1, 100);
    $manager->addMiddleware($middleware2, 50);

    $request = new Request(Method::GET, '/test', [], '', []);
    $stateManager = new RequestStateManager();

    $finalHandler = function (MiddlewareContext $context) use (&$executionOrder) {
        $executionOrder[] = 'handler';

        return $context->withResponse(new HttpResponse(Status::OK));
    };

    $context = $manager->process(new MiddlewareContext($request), $finalHandler, $stateManager);

    expect($executionOrder)->toBe([
        'before-1',
        'before-2',
        'handler',
        'after-2',
        'after-1',
    ]);
});

test('middleware can short-circuit pipeline', function () {
    $executed = [];

    $middleware1 = new class ($executed) implements HttpMiddleware {
        public function __construct(private array &$executed)
        {
        }

        public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
        {
            $this->executed[] = 'middleware-1';

            // Short-circuit by not calling next
            return $context->withResponse(new HttpResponse(Status::FORBIDDEN));
        }
    };

    $middleware2 = new class ($executed) implements HttpMiddleware {
        public function __construct(private array &$executed)
        {
        }

        public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
        {
            $this->executed[] = 'middleware-2';

            return $next($context);
        }
    };

    $manager = new MiddlewareManager();
    $manager->addMiddleware($middleware1, 100);
    $manager->addMiddleware($middleware2, 50);

    $request = new Request(Method::GET, '/test', [], '', []);
    $stateManager = new RequestStateManager();

    $finalHandler = function (MiddlewareContext $context) use (&$executed) {
        $executed[] = 'handler';

        return $context->withResponse(new HttpResponse(Status::OK));
    };

    $context = $manager->process(new MiddlewareContext($request), $finalHandler, $stateManager);

    expect($executed)->toBe(['middleware-1']);
    expect($context->response?->status)->toBe(Status::FORBIDDEN);
});

test('middleware can modify request', function () {
    $modifyMiddleware = new class () implements HttpMiddleware {
        public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
        {
            $modifiedHeaders = $context->request->headers;
            $modifiedHeaders['X-Custom-Header'] = 'test-value';

            $modifiedRequest = new Request(
                $context->request->method,
                $context->request->path,
                $modifiedHeaders,
                $context->request->body,
                $context->request->server
            );

            return $next($context->withRequest($modifiedRequest));
        }
    };

    $manager = new MiddlewareManager();
    $manager->addMiddleware($modifyMiddleware);

    $request = new Request(Method::GET, '/test', [], '', []);
    $stateManager = new RequestStateManager();

    $receivedRequest = null;
    $finalHandler = function (MiddlewareContext $context) use (&$receivedRequest) {
        $receivedRequest = $context->request;

        return $context->withResponse(new HttpResponse(Status::OK));
    };

    $manager->process(new MiddlewareContext($request), $finalHandler, $stateManager);

    expect($receivedRequest?->headers['X-Custom-Header'] ?? null)->toBe('test-value');
});

test('middleware state management works', function () {
    $stateManager = new RequestStateManager();

    $middleware1 = new class () implements HttpMiddleware {
        public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
        {
            $stateManager->set('key1', 'value1');
            $stateManager->set('shared', 'from-middleware-1');

            return $next($context);
        }
    };

    $middleware2 = new class () implements HttpMiddleware {
        public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
        {
            expect($stateManager->get('key1'))->toBe('value1');
            expect($stateManager->get('shared'))->toBe('from-middleware-1');

            $stateManager->set('key2', 'value2');
            $stateManager->set('shared', 'from-middleware-2');

            return $next($context);
        }
    };

    $manager = new MiddlewareManager();
    $manager->addMiddleware($middleware1);
    $manager->addMiddleware($middleware2);

    $request = new Request(Method::GET, '/test', [], '', []);

    $finalHandler = function (MiddlewareContext $context) use ($stateManager) {
        expect($stateManager->get('key1'))->toBe('value1');
        expect($stateManager->get('key2'))->toBe('value2');
        expect($stateManager->get('shared'))->toBe('from-middleware-2');

        return $context->withResponse(new HttpResponse(Status::OK));
    };

    $manager->process(new MiddlewareContext($request), $finalHandler, $stateManager);
});
