<?php

declare(strict_types=1);

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\RateLimitMiddleware;
use App\Framework\Http\Next;
use App\Framework\Http\RequestStateManager;
use App\Framework\Http\ResponseManipulator;
use App\Framework\Http\ServerEnvironment;
use App\Framework\Http\Status;
use App\Framework\RateLimit\RateLimitConfig;
use App\Framework\RateLimit\RateLimiter;
use App\Framework\RateLimit\Storage\StorageInterface;
use App\Framework\RateLimit\TimeProvider\TimeProviderInterface;

beforeEach(function () {
    // Create test storage
    $this->storage = new class () implements StorageInterface {
        public array $requests = [];

        public array $tokenBuckets = [];

        public function getRequestsInWindow(string $key, int $windowStart, int $windowEnd): array
        {
            if (! isset($this->requests[$key])) {
                return [];
            }

            return array_filter(
                $this->requests[$key],
                fn ($timestamp) => $timestamp >= $windowStart && $timestamp <= $windowEnd
            );
        }

        public function addRequest(string $key, int $timestamp, int $ttl): void
        {
            if (! isset($this->requests[$key])) {
                $this->requests[$key] = [];
            }
            $this->requests[$key][] = $timestamp;
        }

        public function getTokenBucket(string $key): ?\App\Framework\RateLimit\TokenBucket
        {
            return $this->tokenBuckets[$key] ?? null;
        }

        public function saveTokenBucket(string $key, \App\Framework\RateLimit\TokenBucket $bucket): void
        {
            $this->tokenBuckets[$key] = $bucket;
        }

        public function clear(string $key): void
        {
            unset($this->requests[$key], $this->tokenBuckets[$key]);
        }

        public function getBaseline(string $key): ?array
        {
            // Simple test implementation - return null for no baseline
            return null;
        }

        public function updateBaseline(string $key, int $rate): void
        {
            // Simple test implementation - do nothing
        }

        public function reset(): void
        {
            $this->requests = [];
            $this->tokenBuckets = [];
        }
    };

    // Create test time provider
    $this->timeProvider = new class () implements TimeProviderInterface {
        public int $currentTime = 1000;

        public function getCurrentTime(): int
        {
            return $this->currentTime;
        }

        public function setTime(int $time): void
        {
            $this->currentTime = $time;
        }
    };

    $this->rateLimiter = new RateLimiter($this->storage, $this->timeProvider);
    $this->responseManipulator = new ResponseManipulator();

    $this->config = new RateLimitConfig(
        enabled: true,
        requestsPerMinute: 10,
        windowSize: 60.0,
        trustedIps: ['192.168.1.100'],
        exemptPaths: ['/health']
    );

    $this->middleware = new RateLimitMiddleware(
        $this->rateLimiter,
        $this->responseManipulator,
        $this->config
    );

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

    $this->stateManager = new RequestStateManager(new WeakMap(), $this->request);
    $this->context = new MiddlewareContext($this->request);
});

afterEach(function () {
    $this->storage->reset();
    $this->timeProvider->setTime(1000);
});

it('allows requests within rate limit', function () {
    $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($this->context, $next, $this->stateManager);

    expect($result->hasResponse())->toBeTrue();
    expect($result->response->status)->toBe(Status::OK);
    expect($result->response->body)->toBe('success');

    // Check rate limit headers
    expect($result->response->headers->has('X-RateLimit-Limit'))->toBeTrue();
    expect($result->response->headers->has('X-RateLimit-Remaining'))->toBeTrue();
    expect($result->response->headers->has('X-RateLimit-Reset'))->toBeTrue();
});

it('blocks requests exceeding rate limit', function () {
    $next = new class () implements Next {
        public function __invoke(MiddlewareContext $context): MiddlewareContext
        {
            return $context->withResponse(new HttpResponse(Status::OK, new Headers(), 'should not reach'));
        }
    };

    // Fill up the rate limit by making actual middleware calls
    for ($i = 0; $i < 10; $i++) {
        $this->middleware->__invoke($this->context, $next, $this->stateManager);
    }

    // This should be blocked
    $result = $this->middleware->__invoke($this->context, $next, $this->stateManager);

    expect($result->hasResponse())->toBeTrue();
    expect($result->response->status)->toBe(Status::TOO_MANY_REQUESTS);

    // Check rate limit headers
    expect($result->response->headers->getFirst('X-RateLimit-Limit'))->toBe('10');
    expect($result->response->headers->getFirst('X-RateLimit-Remaining'))->toBe('0');
    expect($result->response->headers->has('Retry-After'))->toBeTrue();

    // Check JSON response body
    $body = json_decode($result->response->body, true);
    expect($body['error'])->toBe('Rate limit exceeded');
    expect($body['limit'])->toBe(10);
});

it('applies same limit to all endpoints', function () {
    // Test /login endpoint with regular limit (10 requests)
    $loginRequest = new HttpRequest(method: Method::POST, path: '/login');
    $context = new MiddlewareContext($loginRequest);
    $stateManager = new RequestStateManager(new WeakMap(), $loginRequest);

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

    // Fill up the rate limit by making actual middleware calls
    for ($i = 0; $i < 10; $i++) {
        $this->middleware->__invoke($context, $next, $stateManager);
    }

    // This should be blocked
    $result = $this->middleware->__invoke($context, $next, $stateManager);

    expect($result->response->status)->toBe(Status::TOO_MANY_REQUESTS);
    expect($result->response->headers->getFirst('X-RateLimit-Limit'))->toBe('10');
});

it('exempts whitelisted IPs', function () {
    // Use exempt IP
    $exemptRequest = new HttpRequest(
        method: Method::GET,
        path: '/api/test',
        server: new ServerEnvironment(['REMOTE_ADDR' => '192.168.1.100'])
    );

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

    // Fill up rate limit for this IP (should be ignored)
    for ($i = 0; $i < 15; $i++) {
        $this->rateLimiter->checkLimit('ip:192.168.1.100', 10, 60);
    }

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

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

    expect($result->response->status)->toBe(Status::OK);
    expect($result->response->body)->toBe('exempt');
});

it('exempts whitelisted endpoints', function () {
    $healthRequest = new HttpRequest(method: Method::GET, path: '/health');
    $context = new MiddlewareContext($healthRequest);
    $stateManager = new RequestStateManager(new WeakMap(), $healthRequest);

    // Fill up rate limit (should be ignored for /health)
    for ($i = 0; $i < 15; $i++) {
        $this->rateLimiter->checkLimit('ip:127.0.0.1', 10, 60);
    }

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

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

    expect($result->response->status)->toBe(Status::OK);
    expect($result->response->body)->toBe('healthy');
});

it('respects disabled configuration', function () {
    $disabledConfig = new RateLimitConfig(enabled: false);
    $middleware = new RateLimitMiddleware(
        $this->rateLimiter,
        $this->responseManipulator,
        $disabledConfig
    );

    // Fill up rate limit
    for ($i = 0; $i < 15; $i++) {
        $this->rateLimiter->checkLimit('ip:127.0.0.1', 10, 60);
    }

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

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

    expect($result->response->status)->toBe(Status::OK);
    expect($result->response->body)->toBe('disabled');
});

it('passes through when no response is set', function () {
    $next = new class () implements Next {
        public function __invoke(MiddlewareContext $context): MiddlewareContext
        {
            return $context; // No response set
        }
    };

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

    expect($result->hasResponse())->toBeFalse();
});

it('adds correct rate limit headers', function () {
    $next = new class () implements Next {
        public function __invoke(MiddlewareContext $context): MiddlewareContext
        {
            return $context->withResponse(new HttpResponse(Status::OK, new Headers(), 'test'));
        }
    };

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

    $headers = $result->response->headers;
    expect($headers->has('X-RateLimit-Limit'))->toBeTrue();
    expect($headers->has('X-RateLimit-Remaining'))->toBeTrue();
    expect($headers->has('X-RateLimit-Reset'))->toBeTrue();

    expect($headers->getFirst('X-RateLimit-Limit'))->toBe('10');
    expect((int) $headers->getFirst('X-RateLimit-Remaining'))->toBeLessThan(10);
    expect((int) $headers->getFirst('X-RateLimit-Reset'))->toBeGreaterThan(time());
});

it('handles time window properly', function () {
    // Make 9 requests (within limit)
    for ($i = 0; $i < 9; $i++) {
        $result = $this->rateLimiter->checkLimit('ip:127.0.0.1', 10, 60);
        expect($result->isAllowed())->toBeTrue();
    }

    // 10th request should still be allowed
    $next = new class () implements Next {
        public function __invoke(MiddlewareContext $context): MiddlewareContext
        {
            return $context->withResponse(new HttpResponse(Status::OK, new Headers(), 'allowed'));
        }
    };

    $result = $this->middleware->__invoke($this->context, $next, $this->stateManager);
    expect($result->response->status)->toBe(Status::OK);

    // Move time forward beyond window
    $this->timeProvider->setTime(2000); // +1000 seconds

    // Should be allowed again after window reset
    $result2 = $this->middleware->__invoke($this->context, $next, $this->stateManager);
    expect($result2->response->status)->toBe(Status::OK);
});

it('generates different keys for different endpoints', function () {
    $config = new RateLimitConfig(
        enabled: true,
        requestsPerMinute: 5,
        windowSize: 60.0
    );

    $middleware = new RateLimitMiddleware(
        $this->rateLimiter,
        $this->responseManipulator,
        $config
    );

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

    // Make requests to different endpoints - should each have their own limits
    $apiRequest = new HttpRequest(method: Method::GET, path: '/api/users');
    $apiContext = new MiddlewareContext($apiRequest);
    $apiStateManager = new RequestStateManager(new WeakMap(), $apiRequest);

    $webRequest = new HttpRequest(method: Method::GET, path: '/web/dashboard');
    $webContext = new MiddlewareContext($webRequest);
    $webStateManager = new RequestStateManager(new WeakMap(), $webRequest);

    // Both should be allowed since they're different endpoints
    $result1 = $middleware->__invoke($apiContext, $next, $apiStateManager);
    $result2 = $middleware->__invoke($webContext, $next, $webStateManager);

    expect($result1->response->status)->toBe(Status::OK);
    expect($result2->response->status)->toBe(Status::OK);
});
