<?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 ungültige Content-Types
 *
 * Verwendet OWASP-konforme Nachrichten für Content-Type-Validierung
 */
final class InvalidContentTypeException extends FrameworkException
{
    /**
     * @param string $clientIp Client-IP für Security-Tracking
     * @param string $providedContentType Gesendeter Content-Type
     * @param array $allowedContentTypes Erlaubte Content-Types
     * @param string $endpoint Betroffener Endpoint
     * @param \Throwable|null $previous Vorherige Ausnahme
     */
    public function __construct(
        public readonly string $clientIp,
        public readonly string $providedContentType,
        public readonly array $allowedContentTypes,
        public readonly string $endpoint = '',
        ?\Throwable $previous = null
    ) {
        $allowedList = implode(', ', $this->allowedContentTypes);

        // OWASP-konforme Nachricht mit Platzhaltern
        $message = "Client {$this->clientIp} sent invalid content type '{$this->providedContentType}' to {$this->endpoint}";

        $context = ExceptionContext::forOperation('http.content_type_validation', 'Http')
            ->withData([
                'client_ip' => $this->clientIp,
                'provided_content_type' => $this->providedContentType,
                'allowed_content_types' => $this->allowedContentTypes,
                'endpoint' => $this->endpoint,
                'allowed_types_count' => count($this->allowedContentTypes),
                'event_identifier' => "http_invalid_content_type:{$this->clientIp},{$this->endpoint}",
                'category' => 'http_validation',
                'requires_alert' => $this->requiresAlert(),
            ])
            ->withMetadata([
                'security_event' => true,
                'owasp_compliant' => true,
                'log_level' => 'INFO',
                'http_error' => true,
            ]);

        parent::__construct(
            message: $message,
            context: $context,
            code: 415, // Unsupported Media Type
            previous: $previous,
            errorCode: HttpErrorCode::BAD_REQUEST
        );
    }

    // === Factory Methods für verschiedene Content-Type-Szenarien ===

    public static function jsonRequired(string $clientIp, string $providedType, string $endpoint = ''): self
    {
        return new self($clientIp, $providedType, ['application/json'], $endpoint);
    }

    public static function xmlRequired(string $clientIp, string $providedType, string $endpoint = ''): self
    {
        return new self($clientIp, $providedType, ['application/xml', 'text/xml'], $endpoint);
    }

    public static function formDataRequired(string $clientIp, string $providedType, string $endpoint = ''): self
    {
        return new self($clientIp, $providedType, ['application/x-www-form-urlencoded'], $endpoint);
    }

    public static function multipartRequired(string $clientIp, string $providedType, string $endpoint = ''): self
    {
        return new self($clientIp, $providedType, ['multipart/form-data'], $endpoint);
    }

    public static function textRequired(string $clientIp, string $providedType, string $endpoint = ''): self
    {
        return new self($clientIp, $providedType, ['text/plain'], $endpoint);
    }

    public static function apiEndpoint(string $clientIp, string $providedType, string $endpoint = ''): self
    {
        return new self($clientIp, $providedType, [
            'application/json',
            'application/xml',
            'application/x-www-form-urlencoded',
        ], $endpoint);
    }

    public static function fileUpload(string $clientIp, string $providedType, array $allowedTypes, string $endpoint = ''): self
    {
        return new self($clientIp, $providedType, $allowedTypes, $endpoint);
    }

    /**
     * Gibt OWASP-konforme Event-Daten zurück
     */
    public function getSecurityEventData(): array
    {
        return [
            'event_identifier' => "http_invalid_content_type:{$this->clientIp},{$this->endpoint}",
            'description' => "Client {$this->clientIp} sent invalid content type '{$this->providedContentType}' to {$this->endpoint}",
            'category' => 'http_validation',
            'log_level' => 'INFO',
            'requires_alert' => $this->requiresAlert(),
            'client_ip' => $this->clientIp,
            'provided_content_type' => $this->providedContentType,
            'allowed_content_types' => $this->allowedContentTypes,
            'endpoint' => $this->endpoint,
        ];
    }

    /**
     * Prüft ob ein Alert erforderlich ist (bei verdächtigen Content-Types)
     */
    public function requiresAlert(): bool
    {
        // Alert bei potentiell gefährlichen Content-Types
        $dangerousTypes = [
            'application/x-shockwave-flash',
            'application/x-executable',
            'application/octet-stream',
            'text/html', // Wenn nicht erwartet
            'text/javascript',
            'application/javascript',
        ];

        foreach ($dangerousTypes as $dangerous) {
            if (str_contains(strtolower($this->providedContentType), $dangerous)) {
                return true;
            }
        }

        // Alert bei API-Endpoints und ungewöhnlichen Types
        if (str_contains($this->endpoint, '/api/') && ! $this->isCommonApiContentType()) {
            return true;
        }

        return false;
    }

    /**
     * Prüft ob es ein häufiger API-Content-Type ist
     */
    private function isCommonApiContentType(): bool
    {
        $commonApiTypes = [
            'application/json',
            'application/xml',
            'text/xml',
            'application/x-www-form-urlencoded',
            'multipart/form-data',
            'text/plain',
        ];

        foreach ($commonApiTypes as $common) {
            if (str_contains(strtolower($this->providedContentType), $common)) {
                return true;
            }
        }

        return false;
    }

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

        if (empty($this->endpoint)) {
            return "Unsupported content type '{$this->providedContentType}'. Supported types: {$allowedList}";
        }

        return "Endpoint '{$this->endpoint}' does not support content type '{$this->providedContentType}'. Supported types: {$allowedList}";
    }

    /**
     * Formatiert Content-Type-Liste für Benutzer
     */
    private function formatContentTypeList(array $contentTypes): string
    {
        if (count($contentTypes) === 1) {
            return $contentTypes[0];
        }

        if (count($contentTypes) === 2) {
            return $contentTypes[0] . ' and ' . $contentTypes[1];
        }

        $last = array_pop($contentTypes);

        return implode(', ', $contentTypes) . ', and ' . $last;
    }

    /**
     * Schlägt passenden Content-Type vor
     */
    public function getSuggestedContentType(): ?string
    {
        if (empty($this->allowedContentTypes)) {
            return null;
        }

        // Bei API-Endpoints JSON bevorzugen
        if (str_contains($this->endpoint, '/api/') && in_array('application/json', $this->allowedContentTypes)) {
            return 'application/json';
        }

        // Ersten erlaubten Type zurückgeben
        return $this->allowedContentTypes[0];
    }

    /**
     * Gibt HTTP-Header für korrekte Content-Type-Information zurück
     */
    public function getResponseHeaders(): array
    {
        $headers = [
            'Accept' => implode(', ', $this->allowedContentTypes),
        ];

        $suggested = $this->getSuggestedContentType();
        if ($suggested) {
            $headers['X-Suggested-Content-Type'] = $suggested;
        }

        return $headers;
    }

    /**
     * Analysiert den gesendeten Content-Type
     */
    public function analyzeProvidedContentType(): array
    {
        $analysis = [
            'main_type' => '',
            'sub_type' => '',
            'charset' => '',
            'boundary' => '',
            'is_multipart' => false,
            'is_text' => false,
            'is_binary' => false,
        ];

        if (empty($this->providedContentType)) {
            return $analysis;
        }

        // Parse Content-Type
        $parts = explode(';', $this->providedContentType);
        $mainType = trim($parts[0]);

        if (str_contains($mainType, '/')) {
            [$type, $subtype] = explode('/', $mainType, 2);
            $analysis['main_type'] = trim($type);
            $analysis['sub_type'] = trim($subtype);
        }

        // Parse Parameter
        for ($i = 1; $i < count($parts); $i++) {
            $param = trim($parts[$i]);
            if (str_contains($param, '=')) {
                [$key, $value] = explode('=', $param, 2);
                $key = trim($key);
                $value = trim($value, ' "');

                switch ($key) {
                    case 'charset':
                        $analysis['charset'] = $value;

                        break;
                    case 'boundary':
                        $analysis['boundary'] = $value;

                        break;
                }
            }
        }

        // Typ-Analyse
        $analysis['is_multipart'] = str_starts_with($mainType, 'multipart/');
        $analysis['is_text'] = str_starts_with($mainType, 'text/') ||
                              in_array($mainType, ['application/json', 'application/xml']);
        $analysis['is_binary'] = ! $analysis['is_text'] && ! $analysis['is_multipart'];

        return $analysis;
    }

    /**
     * Gibt Empfehlung für Client-Implementierung zurück
     */
    public function getClientRecommendation(): string
    {
        $suggested = $this->getSuggestedContentType();

        if ($suggested) {
            return "Set the Content-Type header to '{$suggested}' for this endpoint.";
        }

        return "Check the API documentation for supported content types for this endpoint.";
    }
}
