<?php

declare(strict_types=1);

namespace App\Framework\Http\Middlewares;

use App\Framework\ErrorHandling\SecurityEventLogger;
use App\Framework\Exception\SecurityEvent\SystemExcessiveUseEvent;
use App\Framework\Http\Headers;
use App\Framework\Http\HttpMiddleware;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\MiddlewareContext;
use App\Framework\Http\MiddlewarePriority;
use App\Framework\Http\MiddlewarePriorityAttribute;
use App\Framework\Http\Next;
use App\Framework\Http\RequestStateManager;
use App\Framework\Http\Response;
use App\Framework\Http\ResponseManipulator;
use App\Framework\Http\Status;
use App\Framework\RateLimit\RateLimitConfig;
use App\Framework\RateLimit\RateLimiter;
use App\Framework\RateLimit\RateLimitResult;

/**
 * Modern Rate Limiting Middleware using the Framework's RateLimit system
 *
 * Supports both sliding window and token bucket algorithms.
 * Provides comprehensive rate limiting with security event logging.
 */
#[MiddlewarePriorityAttribute(MiddlewarePriority::RATE_LIMITING)]
final readonly class RateLimitMiddleware implements HttpMiddleware
{
    public function __construct(
        private RateLimiter $rateLimiter,
        private ResponseManipulator $responseManipulator,
        private RateLimitConfig $config = new RateLimitConfig(),
        private ?SecurityEventLogger $securityLogger = null
    ) {
    }

    public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
    {
        if (! $this->config->enabled) {
            return $next($context);
        }

        $request = $context->request;
        $clientIp = $this->getClientIp($request);

        // Check exemptions
        if ($this->isExempt($clientIp, $request->path)) {
            return $next($context);
        }

        // Check rate limits
        $result = $this->checkRateLimit($clientIp, $request->path);

        if ($result->isExceeded()) {
            return $this->handleRateLimitExceeded($context, $clientIp, $request->path, $result);
        }

        // Proceed with request and add rate limit headers
        $resultContext = $next($context);

        if ($resultContext->hasResponse()) {
            $response = $this->addRateLimitHeaders($resultContext->response, $result);

            return $resultContext->withResponse($response);
        }

        return $resultContext;
    }

    private function getClientIp($request): string
    {
        // Try to get from server environment or fallback
        $serverIp = $request->server->getClientIp();

        return $serverIp ? $serverIp->value : '127.0.0.1';
    }

    private function isExempt(string $clientIp, string $path): bool
    {
        return $this->config->isTrustedIp($clientIp) ||
               $this->config->isExemptPath($path);
    }

    private function checkRateLimit(string $clientIp, string $path): RateLimitResult
    {
        $limit = $this->config->requestsPerMinute;
        $window = $this->config->windowSize;

        // Generate rate limit key
        $key = $this->generateRateLimitKey($clientIp, $path);

        // Use sliding window algorithm by default
        return $this->rateLimiter->checkLimit($key, $limit, $window);
    }

    private function generateRateLimitKey(string $clientIp, string $path): string
    {
        // Simple key generation based on IP and path
        return "ratelimit:ip:{$clientIp}:path:" . md5($path);
    }

    private function handleRateLimitExceeded(
        MiddlewareContext $context,
        string $clientIp,
        string $path,
        RateLimitResult $result
    ): MiddlewareContext {
        // Log security event
        $this->logSecurityEvent($clientIp, $path, $result);

        // Create rate limit exceeded response
        $headers = new Headers([
            'Content-Type' => 'application/json',
            'X-RateLimit-Limit' => (string) $result->getLimit(),
            'X-RateLimit-Remaining' => '0',
            'X-RateLimit-Reset' => (string) (time() + ($result->getRetryAfter() ?? $this->config->windowSize)),
            'Retry-After' => (string) ($result->getRetryAfter() ?? $this->config->windowSize),
        ]);

        $body = json_encode([
            'error' => 'Rate limit exceeded',
            'message' => 'Too many requests. Please slow down.',
            'limit' => $result->getLimit(),
            'current' => $result->getCurrent(),
            'retry_after' => $result->getRetryAfter() ?? $this->config->windowSize,
        ], JSON_PRETTY_PRINT);

        $response = new HttpResponse(Status::TOO_MANY_REQUESTS, $headers, $body);

        return $context->withResponse($response);
    }

    private function addRateLimitHeaders(Response $response, RateLimitResult $result): Response
    {
        $headers = $response->headers
            ->with('X-RateLimit-Limit', (string) $result->getLimit())
            ->with('X-RateLimit-Remaining', (string) $result->getRemainingRequests())
            ->with('X-RateLimit-Reset', (string) (time() + $this->config->windowSize));

        return $this->responseManipulator->withHeaders($response, $headers);
    }

    private function logSecurityEvent(string $clientIp, string $path, RateLimitResult $result): void
    {
        if (! $this->securityLogger) {
            return;
        }

        $event = new SystemExcessiveUseEvent(
            null, // No user ID for IP-based limiting
            $clientIp,
            "Rate limit exceeded for {$path}: {$result->getCurrent()}/{$result->getLimit()} requests"
        );

        $this->securityLogger->log($event);
    }
}
