<?php

declare(strict_types=1);

namespace App\Framework\Exception;

use App\Framework\Http\RequestId;
use App\Framework\UserAgent\UserAgent;

final readonly class RequestContext
{
    public function __construct(
        public ?string $hostIp = null,
        public ?string $hostname = null,
        public ?string $protocol = null,
        public ?string $port = null,
        public ?string $requestUri = null,
        public ?string $requestMethod = null,
        public ?UserAgent $userAgent = null,
        public ?string $clientIp = null,
        public ?RequestId $requestId = null,
        public array $headers = []
    ) {
    }

    // TODO: Get Requestdata from Request Object in Container

    public static function create(
        ?string $clientIp = null,
        ?UserAgent $userAgent = null,
        ?string $requestMethod = null,
        ?string $requestUri = null,
        ?string $hostIp = null,
        ?string $hostname = null,
        ?string $protocol = null,
        ?string $port = null,
        ?RequestId $requestId = null,
        array $headers = []
    ): self {
        return new self(
            hostIp: $hostIp,
            hostname: $hostname,
            protocol: $protocol,
            port: $port,
            requestUri: $requestUri,
            requestMethod: $requestMethod,
            userAgent: $userAgent,
            clientIp: $clientIp,
            requestId: $requestId,
            headers: $headers
        );
    }

    public static function fromGlobals(): self
    {
        return new self(
            hostIp: $_SERVER['SERVER_ADDR'] ?? null,
            hostname: $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? null,
            protocol: isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http',
            port: $_SERVER['SERVER_PORT'] ?? null,
            requestUri: $_SERVER['REQUEST_URI'] ?? null,
            requestMethod: $_SERVER['REQUEST_METHOD'] ?? null,
            userAgent: isset($_SERVER['HTTP_USER_AGENT']) ? UserAgent::fromString($_SERVER['HTTP_USER_AGENT']) : null,
            clientIp: self::getClientIp(),
            headers: self::getHeaders()
        );
    }

    public static function empty(): self
    {
        return new self();
    }

    public function toArray(): array
    {
        return [
            'host_ip' => $this->hostIp,
            'hostname' => $this->hostname,
            'protocol' => $this->protocol,
            'port' => $this->port,
            'request_uri' => $this->requestUri,
            'request_method' => $this->requestMethod,
            'user_agent' => $this->userAgent?->value,
            'client_ip' => $this->clientIp,
            'request_id' => $this->requestId?->toString(),
            'headers' => $this->headers,
        ];
    }

    private static function getClientIp(): ?string
    {
        $ipKeys = [
            'HTTP_CF_CONNECTING_IP',     // Cloudflare
            'HTTP_X_FORWARDED_FOR',      // Load balancer/proxy
            'HTTP_X_FORWARDED',          // Proxy
            'HTTP_X_CLUSTER_CLIENT_IP',  // Cluster
            'HTTP_FORWARDED_FOR',        // Proxy
            'HTTP_FORWARDED',            // Proxy
            'REMOTE_ADDR',                // Standard
        ];

        foreach ($ipKeys as $key) {
            if (! empty($_SERVER[$key])) {
                $ips = explode(',', $_SERVER[$key]);
                $ip = trim($ips[0]);
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }

        return $_SERVER['REMOTE_ADDR'] ?? null;
    }

    private static function getHeaders(): array
    {
        $headers = [];
        $maxHeaders = 20; // EMERGENCY: Limit to 20 headers max
        $maxHeaderSize = 1024; // EMERGENCY: Max 1KB per header
        $count = 0;

        foreach ($_SERVER as $key => $value) {
            if (str_starts_with($key, 'HTTP_')) {
                // EMERGENCY: Prevent memory exhaustion from massive headers
                if (++$count > $maxHeaders) {
                    $headers['X-Headers-Truncated'] = 'true';

                    break;
                }

                // EMERGENCY: Truncate massive header values
                if (strlen($value) > $maxHeaderSize) {
                    $value = substr($value, 0, $maxHeaderSize) . '...[TRUNCATED]';
                }

                $header = str_replace('_', '-', substr($key, 5));
                $header = ucwords(strtolower($header), '-');
                $headers[$header] = $value;
            }
        }

        return $headers;
    }
}
