<?php

namespace App\Framework\Http;

/**
 * Kapselt Server-Environment-Daten aus $_SERVER
 */
final readonly class ServerEnvironment
{
    public function __construct(
        private array $serverData = []
    ) {}

    public static function fromGlobals(): self
    {
        return new self($_SERVER);
    }

    public function get(string|ServerKey $key, mixed $default = null): mixed
    {
        if($key instanceof ServerKey) {
            $key = $key->value;
        }

        return $this->serverData[$key] ?? $default;
    }

    public function has(string|ServerKey $key): bool
    {
        !$key instanceof ServerKey ?: $key = $key->value;

        return array_key_exists($key, $this->serverData);
    }

    // Häufig verwendete Server-Informationen als typisierte Methoden
    public function getRemoteAddr(): IpAddress
    {
        $ip = $this->get(ServerKey::REMOTE_ADDR, '0.0.0.0');
        return new IpAddress($ip);
    }

    public function getUserAgent(): string
    {
        return $this->get(ServerKey::HTTP_USER_AGENT, '');
    }

    public function getServerName(): string
    {
        return $this->get(ServerKey::SERVER_NAME, '');
    }

    public function getServerPort(): int
    {
        return (int) $this->get(ServerKey::SERVER_PORT, 80);
    }

    public function getRequestUri(): Uri
    {
        $uriString = $this->get(ServerKey::REQUEST_URI, '/');
        return new Uri($uriString);
    }

    public function getScriptName(): string
    {
        return $this->get(ServerKey::SCRIPT_NAME, '');
    }

    public function getHttpHost(): string
    {
        return $this->get(ServerKey::HTTP_HOST, '');
    }

    public function isHttps(): bool
    {
        return $this->get(ServerKey::HTTPS) === 'on' ||
            $this->get(ServerKey::SERVER_PORT) === '443' ||
            $this->get(ServerKey::HTTP_X_FORWARDED_PROTO) === 'https';
    }

    public function getRequestMethod(): Method
    {
        $methodString = $this->get(ServerKey::REQUEST_METHOD, 'GET');
        return Method::tryFrom($methodString) ?? Method::GET;

    }

    public function getQueryString(): string
    {
        return $this->get(ServerKey::QUERY_STRING, '');
    }

    public function getClientIp(): IpAddress
    {
        return new IpAddress($this->getClientIpString());
    }

    public function getProtocol(): ServerProtocol
    {
        $protocol = $this->get(ServerKey::SERVER_PROTOCOL);
        return ServerProtocol::tryFrom($protocol) ?? ServerProtocol::HTTP_1_0;
    }

    private function getClientIpString(): string
    {
        // Priorisierte IP-Erkennung
        $candidates = [
            ServerKey::HTTP_X_REAL_IP,
            ServerKey::HTTP_X_FORWARDED_FOR,
            ServerKey::REMOTE_ADDR
        ];

        foreach ($candidates as $key) {
            $ip = $this->get($key);
            if ($ip) {
                // Bei X-Forwarded-For kann es mehrere IPs geben
                if ($key === ServerKey::HTTP_X_FORWARDED_FOR) {
                    $ips = explode(',', $ip);
                    return trim($ips[0]);
                }
                return $ip;
            }
        }

        return '0.0.0.0';
    }

    public function getReferer(): string
    {
        return $this->get(ServerKey::HTTP_REFERER, '');
    }

    public function getRefererUri(): Uri
    {
        $referer = $this->getReferer();

        return new Uri($referer);
    }

    /**
     * Prüft ob ein Referer gesetzt ist
     */
    public function hasReferer(): bool
    {
        return !empty($this->get(ServerKey::HTTP_REFERER, null));
    }

    /**
     * Prüft ob der Referer von der gleichen Domain stammt
     */
    public function isRefererSameDomain(): bool
    {
        $referer = $this->getReferer();

        if (empty($referer)) {
            return false;
        }

        $refererHost = parse_url($referer, PHP_URL_HOST);
        $currentHost = $this->getHttpHost();

        return $refererHost === $currentHost;
    }

    /**
     * Sichere Referer-URL für Redirects
     */
    public function getSafeRefererUrl(string $fallback = '/'): string
    {
        $referer = $this->getReferer();

        // Kein Referer gesetzt
        if (empty($referer)) {
            return $fallback;
        }

        // Nur interne Referer erlauben (CSRF-Schutz)
        if (!$this->isRefererSameDomain()) {
            return $fallback;
        }

        return $referer;
    }

}
