<?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 fehlerhaft formatiertes JSON
 *
 * Verwendet OWASP-konforme Nachrichten für JSON-Parsing-Fehler
 */
final class MalformedJsonException extends FrameworkException
{
    /**
     * @param string $clientIp Client-IP für Security-Tracking
     * @param string $jsonError JSON-Parser-Fehlermeldung
     * @param int $jsonErrorCode JSON-Error-Code
     * @param string $jsonData Ursprüngliche JSON-Daten (gekürzt)
     * @param \Throwable|null $previous Vorherige Ausnahme
     */
    public function __construct(
        public readonly string $clientIp,
        public readonly string $jsonError,
        public readonly int $jsonErrorCode = JSON_ERROR_SYNTAX,
        public readonly string $jsonData = '',
        ?\Throwable $previous = null
    ) {
        // OWASP-konforme Nachricht mit Platzhaltern
        $message = "Client {$this->clientIp} sent malformed JSON: {$this->jsonError}";

        $context = ExceptionContext::forOperation('http.json_parsing', 'Http')
            ->withData([
                'client_ip' => $this->clientIp,
                'json_error' => $this->jsonError,
                'json_error_code' => $this->jsonErrorCode,
                'json_data_length' => strlen($this->jsonData),
                'event_identifier' => "http_malformed_json:{$this->clientIp}",
                'category' => 'http_validation',
                'requires_alert' => false,
            ])
            ->withDebug([
                'json_data_sample' => substr($this->jsonData, 0, 100), // Erste 100 Zeichen für Debug
            ])
            ->withMetadata([
                'security_event' => true,
                'owasp_compliant' => true,
                'log_level' => 'INFO',
                'http_error' => true,
            ]);

        parent::__construct(
            message: $message,
            context: $context,
            code: 400, // Bad Request
            previous: $previous,
            errorCode: HttpErrorCode::BAD_REQUEST
        );
    }

    // === Factory Methods für verschiedene JSON-Fehler ===

    public static function syntaxError(string $clientIp, string $jsonData = ''): self
    {
        return new self($clientIp, 'Syntax error', JSON_ERROR_SYNTAX, $jsonData);
    }

    public static function depthExceeded(string $clientIp, string $jsonData = ''): self
    {
        return new self($clientIp, 'Maximum stack depth exceeded', JSON_ERROR_DEPTH, $jsonData);
    }

    public static function ctrlCharError(string $clientIp, string $jsonData = ''): self
    {
        return new self($clientIp, 'Unexpected control character found', JSON_ERROR_CTRL_CHAR, $jsonData);
    }

    public static function utf8Error(string $clientIp, string $jsonData = ''): self
    {
        return new self($clientIp, 'Malformed UTF-8 characters', JSON_ERROR_UTF8, $jsonData);
    }

    public static function fromJsonLastError(string $clientIp, string $jsonData = ''): self
    {
        $error = json_last_error_msg();
        $code = json_last_error();

        return new self($clientIp, $error, $code, $jsonData);
    }

    /**
     * Gibt OWASP-konforme Event-Daten zurück
     */
    public function getSecurityEventData(): array
    {
        return [
            'event_identifier' => "http_malformed_json:{$this->clientIp}",
            'description' => "Client {$this->clientIp} sent malformed JSON: {$this->jsonError}",
            'category' => 'http_validation',
            'log_level' => 'INFO',
            'requires_alert' => false,
            'client_ip' => $this->clientIp,
            'json_error' => $this->jsonError,
            'json_error_code' => $this->jsonErrorCode,
        ];
    }

    /**
     * Gibt benutzerfreundliche Fehlermeldung zurück
     */
    public function getUserMessage(): string
    {
        return match ($this->jsonErrorCode) {
            JSON_ERROR_SYNTAX => 'Invalid JSON format. Please check your JSON syntax.',
            JSON_ERROR_DEPTH => 'JSON structure too complex. Please reduce nesting depth.',
            JSON_ERROR_CTRL_CHAR => 'Invalid characters in JSON. Please check for control characters.',
            JSON_ERROR_UTF8 => 'Invalid UTF-8 encoding in JSON. Please check character encoding.',
            default => 'Invalid JSON format. Please check your request body.',
        };
    }

    /**
     * Gibt detaillierte Fehlerbeschreibung für Entwickler zurück
     */
    public function getDeveloperMessage(): string
    {
        $position = $this->findErrorPosition();
        $positionInfo = $position ? " at position {$position}" : '';

        return "JSON parsing failed: {$this->jsonError}{$positionInfo}";
    }

    /**
     * Versucht die Position des JSON-Fehlers zu finden
     */
    private function findErrorPosition(): ?int
    {
        if (empty($this->jsonData)) {
            return null;
        }

        // Vereinfachte Positionsbestimmung
        // In einer echten Implementierung könnte hier ein detaillierterer Parser verwendet werden
        $testData = $this->jsonData;
        $position = 0;

        while ($position < strlen($testData)) {
            $substr = substr($testData, 0, $position + 1);
            json_decode($substr);

            if (json_last_error() !== JSON_ERROR_NONE) {
                return $position;
            }

            $position++;
        }

        return null;
    }

    /**
     * Gibt JSON-Error-Code als String zurück
     */
    public function getJsonErrorName(): string
    {
        return match ($this->jsonErrorCode) {
            JSON_ERROR_NONE => 'JSON_ERROR_NONE',
            JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH',
            JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH',
            JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR',
            JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX',
            JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8',
            JSON_ERROR_RECURSION => 'JSON_ERROR_RECURSION',
            JSON_ERROR_INF_OR_NAN => 'JSON_ERROR_INF_OR_NAN',
            JSON_ERROR_UNSUPPORTED_TYPE => 'JSON_ERROR_UNSUPPORTED_TYPE',
            default => 'UNKNOWN_JSON_ERROR',
        };
    }

    /**
     * Prüft ob der Fehler ein häufiger Entwicklerfehler ist
     */
    public function isCommonDeveloperError(): bool
    {
        return in_array($this->jsonErrorCode, [
            JSON_ERROR_SYNTAX,
            JSON_ERROR_CTRL_CHAR,
            JSON_ERROR_UTF8,
        ]);
    }
}
