<?php

declare(strict_types=1);

namespace App\Framework\Exception\Http;

use App\Framework\Exception\Core\HttpErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;

/**
 * Ausnahme für Rate-Limit-Überschreitungen
 *
 * Verwendet OWASP-konforme Nachrichten für Rate-Limiting-Violations
 */
final class RateLimitExceededException extends FrameworkException
{
    /**
     * @param string $clientIp Client-IP für Security-Tracking
     * @param int $currentRequests Aktuelle Anzahl Requests
     * @param int $maxRequests Maximale erlaubte Requests
     * @param int $windowSeconds Zeitfenster in Sekunden
     * @param int $retryAfterSeconds Warten bis nächster Request erlaubt
     * @param string $limitType Art des Limits (ip, user, api_key, etc.)
     * @param \Throwable|null $previous Vorherige Ausnahme
     */
    public function __construct(
        public readonly string $clientIp,
        public readonly int $currentRequests,
        public readonly int $maxRequests,
        public readonly int $windowSeconds,
        public readonly int $retryAfterSeconds,
        public readonly string $limitType = 'ip',
        ?\Throwable $previous = null
    ) {
        // OWASP-konforme Nachricht mit Platzhaltern
        $message = "Client {$this->clientIp} exceeded rate limit: {$this->currentRequests}/{$this->maxRequests} requests in {$this->windowSeconds}s";

        $context = ExceptionContext::forOperation('http.rate_limiting', 'Http')
            ->withData([
                'client_ip' => $this->clientIp,
                'current_requests' => $this->currentRequests,
                'max_requests' => $this->maxRequests,
                'window_seconds' => $this->windowSeconds,
                'retry_after_seconds' => $this->retryAfterSeconds,
                'limit_type' => $this->limitType,
                'usage_percentage' => round(($this->currentRequests / $this->maxRequests) * 100, 2),
                'event_identifier' => "http_rate_limit_exceeded:{$this->clientIp},{$this->limitType}",
                'category' => 'rate_limiting',
                'requires_alert' => $this->requiresAlert(),
            ])
            ->withMetadata([
                'security_event' => true,
                'owasp_compliant' => true,
                'log_level' => 'WARN',
                'http_error' => true,
            ]);

        parent::__construct(
            message: $message,
            context: $context,
            code: 429, // Too Many Requests
            previous: $previous,
            errorCode: HttpErrorCode::RATE_LIMIT_EXCEEDED,
            retryAfter: $this->retryAfterSeconds
        );
    }

    // === Factory Methods für verschiedene Rate-Limit-Szenarien ===

    public static function ipLimit(string $clientIp, int $current, int $max, int $windowSeconds = 3600): self
    {
        $retryAfter = min(3600, $windowSeconds); // Maximal 1 Stunde

        return new self($clientIp, $current, $max, $windowSeconds, $retryAfter, 'ip');
    }

    public static function userLimit(string $clientIp, string $userId, int $current, int $max, int $windowSeconds = 3600): self
    {
        $retryAfter = min(1800, $windowSeconds); // Maximal 30 Minuten für User

        return new self($clientIp, $current, $max, $windowSeconds, $retryAfter, 'user');
    }

    public static function apiKeyLimit(string $clientIp, string $apiKey, int $current, int $max, int $windowSeconds = 3600): self
    {
        $retryAfter = min(900, $windowSeconds); // Maximal 15 Minuten für API Keys

        return new self($clientIp, $current, $max, $windowSeconds, $retryAfter, 'api_key');
    }

    public static function endpointLimit(string $clientIp, string $endpoint, int $current, int $max, int $windowSeconds = 300): self
    {
        $retryAfter = min(300, $windowSeconds); // Maximal 5 Minuten für Endpoints

        return new self($clientIp, $current, $max, $windowSeconds, $retryAfter, 'endpoint');
    }

    public static function globalLimit(string $clientIp, int $current, int $max, int $windowSeconds = 60): self
    {
        $retryAfter = min(60, $windowSeconds); // Maximal 1 Minute für Global

        return new self($clientIp, $current, $max, $windowSeconds, $retryAfter, 'global');
    }

    /**
     * Gibt OWASP-konforme Event-Daten zurück
     */
    public function getSecurityEventData(): array
    {
        return [
            'event_identifier' => "http_rate_limit_exceeded:{$this->clientIp},{$this->limitType}",
            'description' => "Client {$this->clientIp} exceeded rate limit: {$this->currentRequests}/{$this->maxRequests} requests in {$this->windowSeconds}s",
            'category' => 'rate_limiting',
            'log_level' => 'WARN',
            'requires_alert' => $this->requiresAlert(),
            'client_ip' => $this->clientIp,
            'limit_type' => $this->limitType,
            'current_requests' => $this->currentRequests,
            'max_requests' => $this->maxRequests,
            'usage_percentage' => $this->getUsagePercentage(),
        ];
    }

    /**
     * Prüft ob ein Alert erforderlich ist (bei extremen Überschreitungen)
     */
    public function requiresAlert(): bool
    {
        $usagePercentage = $this->getUsagePercentage();

        // Alert bei extremen Überschreitungen (>200% des Limits)
        if ($usagePercentage > 200) {
            return true;
        }

        // Alert bei sehr hohen Request-Zahlen (potentielle DDoS)
        if ($this->currentRequests > 1000) {
            return true;
        }

        // Alert bei API-Key-Limits (könnte kompromittiert sein)
        if ($this->limitType === 'api_key' && $usagePercentage > 150) {
            return true;
        }

        return false;
    }

    /**
     * Berechnet Nutzungsprozentsatz
     */
    public function getUsagePercentage(): float
    {
        return round(($this->currentRequests / $this->maxRequests) * 100, 2);
    }

    /**
     * Prüft ob es ein potentieller DDoS-Angriff ist
     */
    public function isPotentialDDoS(): bool
    {
        return $this->currentRequests > 1000 || $this->getUsagePercentage() > 500;
    }

    /**
     * Gibt benutzerfreundliche Fehlermeldung zurück
     */
    public function getUserMessage(): string
    {
        $waitTime = $this->getWaitTimeMessage();

        return match ($this->limitType) {
            'ip' => "Too many requests from your IP address. Please wait {$waitTime} before trying again.",
            'user' => "You have exceeded your request limit. Please wait {$waitTime} before trying again.",
            'api_key' => "API key rate limit exceeded. Please wait {$waitTime} before making more requests.",
            'endpoint' => "Too many requests to this endpoint. Please wait {$waitTime} before trying again.",
            'global' => "System is busy. Please wait {$waitTime} and try again.",
            default => "Rate limit exceeded. Please wait {$waitTime} before trying again.",
        };
    }

    /**
     * Gibt Wartezeit als benutzerfreundlichen String zurück
     */
    public function getWaitTimeMessage(): string
    {
        if ($this->retryAfterSeconds < 60) {
            return "{$this->retryAfterSeconds} seconds";
        } elseif ($this->retryAfterSeconds < 3600) {
            $minutes = ceil($this->retryAfterSeconds / 60);

            return "{$minutes} minutes";
        } else {
            $hours = ceil($this->retryAfterSeconds / 3600);

            return "{$hours} hours";
        }
    }

    /**
     * Gibt HTTP-Header für Rate-Limiting zurück
     */
    public function getRateLimitHeaders(): array
    {
        return [
            'X-RateLimit-Limit' => (string) $this->maxRequests,
            'X-RateLimit-Remaining' => (string) max(0, $this->maxRequests - $this->currentRequests),
            'X-RateLimit-Reset' => (string) (time() + $this->retryAfterSeconds),
            'X-RateLimit-Window' => (string) $this->windowSeconds,
            'Retry-After' => (string) $this->retryAfterSeconds,
        ];
    }

    /**
     * Berechnet wann der nächste Request erlaubt ist
     */
    public function getNextAllowedTime(): \DateTimeImmutable
    {
        return new \DateTimeImmutable("+{$this->retryAfterSeconds} seconds");
    }

    /**
     * Gibt Empfehlung für Client-Implementierung zurück
     */
    public function getClientRecommendation(): string
    {
        return match ($this->limitType) {
            'ip', 'global' => 'Implement exponential backoff and respect Retry-After headers.',
            'user' => 'Consider caching responses and batching requests to reduce API calls.',
            'api_key' => 'Review your API usage patterns and consider upgrading your plan if needed.',
            'endpoint' => 'This endpoint has specific rate limits. Consider alternative endpoints or batch operations.',
            default => 'Implement proper rate limiting in your client application.',
        };
    }
}
