<?php

declare(strict_types=1);

use App\Framework\DateTime\Clock;
use App\Framework\Http\Headers;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\Method;
use App\Framework\Http\MiddlewareContext;
use App\Framework\Http\Middlewares\CsrfMiddleware;
use App\Framework\Http\Next;
use App\Framework\Http\RequestBody;
use App\Framework\Http\RequestStateManager;
use App\Framework\Http\Session\SessionId;
use App\Framework\Http\Session\SessionInterface;
use App\Framework\Http\Status;
use App\Framework\Security\CsrfToken;
use App\Framework\Security\CsrfTokenGenerator;

beforeEach(function () {
    // Create test CSRF protection mock
    $this->csrfProtection = new class () {
        public array $validatedTokens = [];

        public bool $shouldValidate = true;

        public function validateToken(string $formId, CsrfToken $token): bool
        {
            $this->validatedTokens[] = ['formId' => $formId, 'token' => $token->toString()];

            return $this->shouldValidate;
        }

        public function rotateToken(string $formId): CsrfToken
        {
            return CsrfToken::fromString(str_repeat('a', 64));
        }
    };

    // Create test session
    $this->session = new class ($this->csrfProtection) implements SessionInterface {
        public function __construct(public $csrf)
        {
        }

        public function get(string $key, mixed $default = null): mixed
        {
            return $default;
        }

        public function set(string $key, mixed $value): void
        {
        }

        public function has(string $key): bool
        {
            return false;
        }

        public function remove(string $key): void
        {
        }

        public function clear(): void
        {
        }

        public function all(): array
        {
            return [];
        }

        public static function fromArray(SessionId $sessionId, Clock $clock, CsrfTokenGenerator $csrfTokenGenerator, array $data): self
        {
            return new self(new class () {
                public function validateToken(string $formId, CsrfToken $token): bool
                {
                    return true;
                }

                public function rotateToken(string $formId): CsrfToken
                {
                    return CsrfToken::fromString(str_repeat('a', 64));
                }
            });
        }
    };

    // Create test container mock
    $this->container = new class ($this->session) implements \App\Framework\DI\Container {
        public \App\Framework\DI\MethodInvoker $invoker {
            get => new \App\Framework\DI\MethodInvoker(new \App\Framework\DI\DependencyResolver(new \App\Framework\DI\DefaultContainer()));
        }

        public function __construct(private $session)
        {
        }

        public function get(string $class): object
        {
            if ($class === \App\Framework\Http\Session\SessionInterface::class) {
                return $this->session;
            }

            throw new \RuntimeException("Service not found: $class");
        }

        public function has(string $class): bool
        {
            return $class === \App\Framework\Http\Session\SessionInterface::class;
        }

        public function bind(string $abstract, callable|string|object $concrete): void
        {
        }

        public function singleton(string $abstract, callable|string|object $concrete): void
        {
        }

        public function instance(string $abstract, object $instance): void
        {
        }

        public function forget(string $class): void
        {
        }
    };

    $this->middleware = new CsrfMiddleware($this->container);

    // Create test request
    $this->getRequest = new HttpRequest(
        method: Method::GET,
        path: '/test'
    );

    $this->stateManager = new RequestStateManager(new WeakMap(), $this->getRequest);
});

it('allows GET requests without CSRF validation', function () {
    $context = new MiddlewareContext($this->getRequest);

    $next = new class () implements Next {
        public function __invoke(MiddlewareContext $context): MiddlewareContext
        {
            return $context->withResponse(new HttpResponse(Status::OK, new Headers(), 'success'));
        }
    };

    $result = $this->middleware->__invoke($context, $next, $this->stateManager);

    expect($result->hasResponse())->toBeTrue();
    expect($result->response->status)->toBe(Status::OK);
    expect($this->csrfProtection->validatedTokens)->toBeEmpty();
});

it('validates CSRF token for POST requests', function () {
    // Create POST request with CSRF data
    // Use multipart/form-data content type so the $post array is used
    $headers = new Headers([
        'Content-Type' => 'multipart/form-data',
    ]);

    $requestBody = new RequestBody(Method::POST, $headers, '', [
        '_form_id' => 'contact-form',
        '_token' => str_repeat('b', 64),
    ]);

    $postRequest = new HttpRequest(
        method: Method::POST,
        headers: $headers,
        path: '/contact',
        parsedBody: $requestBody
    );

    $context = new MiddlewareContext($postRequest);
    $stateManager = new RequestStateManager(new WeakMap(), $postRequest);

    $next = new class () implements Next {
        public function __invoke(MiddlewareContext $context): MiddlewareContext
        {
            return $context->withResponse(new HttpResponse(Status::OK, new Headers(), 'success'));
        }
    };

    $result = $this->middleware->__invoke($context, $next, $stateManager);

    expect($result->hasResponse())->toBeTrue();
    expect($result->response->status)->toBe(Status::OK);
    expect($this->csrfProtection->validatedTokens)->toHaveCount(1);
    expect($this->csrfProtection->validatedTokens[0]['formId'])->toBe('contact-form');
    expect($this->csrfProtection->validatedTokens[0]['token'])->toBe(str_repeat('b', 64));
});

it('validates CSRF token from headers for POST requests', function () {
    // Create POST request with CSRF data in headers
    $headers = new Headers([
        'X-CSRF-Form-ID' => 'api-form',
        'X-CSRF-Token' => str_repeat('c', 64),
        'Content-Type' => 'application/json',
    ]);

    $postRequest = new HttpRequest(
        method: Method::POST,
        headers: $headers,
        path: '/api/submit',
        parsedBody: new RequestBody(Method::POST, $headers, '{"data": "test"}', [])
    );

    $context = new MiddlewareContext($postRequest);
    $stateManager = new RequestStateManager(new WeakMap(), $postRequest);

    $next = new class () implements Next {
        public function __invoke(MiddlewareContext $context): MiddlewareContext
        {
            return $context->withResponse(new HttpResponse(Status::OK, new Headers(), 'success'));
        }
    };

    $result = $this->middleware->__invoke($context, $next, $stateManager);

    expect($result->hasResponse())->toBeTrue();
    expect($this->csrfProtection->validatedTokens)->toHaveCount(1);
    expect($this->csrfProtection->validatedTokens[0]['formId'])->toBe('api-form');
    expect($this->csrfProtection->validatedTokens[0]['token'])->toBe(str_repeat('c', 64));
});

it('validates CSRF token for PUT requests', function () {
    $headers = new Headers([
        'Content-Type' => 'multipart/form-data',
    ]);

    $requestBody = new RequestBody(Method::PUT, $headers, '', [
        '_form_id' => 'update-form',
        '_token' => str_repeat('d', 64),
    ]);

    $putRequest = new HttpRequest(
        method: Method::PUT,
        headers: $headers,
        path: '/update',
        parsedBody: $requestBody
    );

    $context = new MiddlewareContext($putRequest);
    $stateManager = new RequestStateManager(new WeakMap(), $putRequest);

    $next = new class () implements Next {
        public function __invoke(MiddlewareContext $context): MiddlewareContext
        {
            return $context->withResponse(new HttpResponse(Status::OK, new Headers(), 'success'));
        }
    };

    $result = $this->middleware->__invoke($context, $next, $stateManager);

    expect($this->csrfProtection->validatedTokens)->toHaveCount(1);
    expect($this->csrfProtection->validatedTokens[0]['formId'])->toBe('update-form');
});

it('validates CSRF token for DELETE requests', function () {
    $headers = new Headers([
        'Content-Type' => 'multipart/form-data',
    ]);

    $requestBody = new RequestBody(Method::DELETE, $headers, '', [
        '_form_id' => 'delete-form',
        '_token' => str_repeat('e', 64),
    ]);

    $deleteRequest = new HttpRequest(
        method: Method::DELETE,
        headers: $headers,
        path: '/delete',
        parsedBody: $requestBody
    );

    $context = new MiddlewareContext($deleteRequest);
    $stateManager = new RequestStateManager(new WeakMap(), $deleteRequest);

    $next = new class () implements Next {
        public function __invoke(MiddlewareContext $context): MiddlewareContext
        {
            return $context->withResponse(new HttpResponse(Status::OK, new Headers(), 'success'));
        }
    };

    $result = $this->middleware->__invoke($context, $next, $stateManager);

    expect($this->csrfProtection->validatedTokens)->toHaveCount(1);
    expect($this->csrfProtection->validatedTokens[0]['formId'])->toBe('delete-form');
});

it('skips CSRF validation when session is not available', function () {
    // Create container that throws exception when getting session
    $failingContainer = new class () implements \App\Framework\DI\Container {
        public \App\Framework\DI\MethodInvoker $invoker {
            get => new \App\Framework\DI\MethodInvoker(new \App\Framework\DI\DependencyResolver(new \App\Framework\DI\DefaultContainer()));
        }

        public function get(string $class): object
        {
            throw new \RuntimeException("Service not found: $class");
        }

        public function has(string $class): bool
        {
            return false;
        }

        public function bind(string $abstract, callable|string|object $concrete): void
        {
        }

        public function singleton(string $abstract, callable|string|object $concrete): void
        {
        }

        public function instance(string $abstract, object $instance): void
        {
        }

        public function forget(string $class): void
        {
        }
    };

    $middleware = new CsrfMiddleware($failingContainer);

    $postRequest = new HttpRequest(
        method: Method::POST,
        path: '/test',
        parsedBody: new RequestBody(Method::POST, new Headers(), '', [])
    );

    $context = new MiddlewareContext($postRequest);
    $stateManager = new RequestStateManager(new WeakMap(), $postRequest);

    $next = new class () implements Next {
        public function __invoke(MiddlewareContext $context): MiddlewareContext
        {
            return $context->withResponse(new HttpResponse(Status::OK, new Headers(), 'success'));
        }
    };

    $result = $middleware->__invoke($context, $next, $stateManager);

    // Should skip CSRF validation and proceed
    expect($result->hasResponse())->toBeTrue();
    expect($result->response->status)->toBe(Status::OK);
});

it('throws exception when form ID is missing', function () {
    $headers = new Headers([
        'Content-Type' => 'multipart/form-data',
    ]);

    $requestBody = new RequestBody(Method::POST, $headers, '', [
        '_token' => str_repeat('f', 64),
        // Missing _form_id
    ]);

    $postRequest = new HttpRequest(
        method: Method::POST,
        headers: $headers,
        path: '/test',
        parsedBody: $requestBody
    );

    $context = new MiddlewareContext($postRequest);
    $stateManager = new RequestStateManager(new WeakMap(), $postRequest);

    $next = new class () implements Next {
        public function __invoke(MiddlewareContext $context): MiddlewareContext
        {
            return $context;
        }
    };

    $this->middleware->__invoke($context, $next, $stateManager);
})->throws(InvalidArgumentException::class, 'CSRF protection requires both form ID and token');

it('throws exception when token is missing', function () {
    $headers = new Headers([
        'Content-Type' => 'multipart/form-data',
    ]);

    $requestBody = new RequestBody(Method::POST, $headers, '', [
        '_form_id' => 'test-form',
        // Missing _token
    ]);

    $postRequest = new HttpRequest(
        method: Method::POST,
        headers: $headers,
        path: '/test',
        parsedBody: $requestBody
    );

    $context = new MiddlewareContext($postRequest);
    $stateManager = new RequestStateManager(new WeakMap(), $postRequest);

    $next = new class () implements Next {
        public function __invoke(MiddlewareContext $context): MiddlewareContext
        {
            return $context;
        }
    };

    $this->middleware->__invoke($context, $next, $stateManager);
})->throws(InvalidArgumentException::class, 'CSRF protection requires both form ID and token');

it('throws exception when token validation fails', function () {
    // Set CSRF protection to fail validation
    $this->csrfProtection->shouldValidate = false;

    $headers = new Headers([
        'Content-Type' => 'multipart/form-data',
    ]);

    $requestBody = new RequestBody(Method::POST, $headers, '', [
        '_form_id' => 'test-form',
        '_token' => str_repeat('f', 64),
    ]);

    $postRequest = new HttpRequest(
        method: Method::POST,
        headers: $headers,
        path: '/test',
        parsedBody: $requestBody
    );

    $context = new MiddlewareContext($postRequest);
    $stateManager = new RequestStateManager(new WeakMap(), $postRequest);

    $next = new class () implements Next {
        public function __invoke(MiddlewareContext $context): MiddlewareContext
        {
            return $context;
        }
    };

    $this->middleware->__invoke($context, $next, $stateManager);
})->throws(RuntimeException::class, 'CSRF token validation failed. This may indicate a security threat.');

it('throws exception for invalid token format', function () {
    $headers = new Headers([
        'Content-Type' => 'multipart/form-data',
    ]);

    $requestBody = new RequestBody(Method::POST, $headers, '', [
        '_form_id' => 'test-form',
        '_token' => 'invalid-token', // Too short and not hex
    ]);

    $postRequest = new HttpRequest(
        method: Method::POST,
        headers: $headers,
        path: '/test',
        parsedBody: $requestBody
    );

    $context = new MiddlewareContext($postRequest);
    $stateManager = new RequestStateManager(new WeakMap(), $postRequest);

    $next = new class () implements Next {
        public function __invoke(MiddlewareContext $context): MiddlewareContext
        {
            return $context;
        }
    };

    $this->middleware->__invoke($context, $next, $stateManager);
})->throws(InvalidArgumentException::class, 'Invalid CSRF token format');
