<?php

declare(strict_types=1);

namespace App\Framework\Exception\Security;

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

/**
 * Ausnahme für XSS-Angriffs-Versuche
 *
 * Verwendet OWASP-konforme Nachrichten für XSS-Detection
 */
final class XssAttemptException extends FrameworkException
{
    /**
     * @param string $clientIp Client-IP für Security-Tracking
     * @param string $field Betroffenes Feld/Parameter
     * @param string $pattern Erkanntes XSS-Pattern
     * @param string $originalValue Ursprünglicher Wert (sanitized)
     * @param string $xssType Art des XSS (reflected, stored, dom)
     * @param \Throwable|null $previous Vorherige Ausnahme
     */
    public function __construct(
        public readonly string $clientIp,
        public readonly string $field,
        public readonly string $pattern,
        public readonly string $originalValue,
        public readonly string $xssType = 'reflected',
        ?\Throwable $previous = null
    ) {
        // OWASP-konforme Nachricht mit Platzhaltern
        $message = "Client {$this->clientIp} attempted XSS attack in field {$this->field}";

        $context = ExceptionContext::forOperation('security.xss_detection', 'Security')
            ->withData([
                'client_ip' => $this->clientIp,
                'field' => $this->field,
                'pattern' => $this->pattern,
                'xss_type' => $this->xssType,
                'value_length' => strlen($this->originalValue),
                'event_identifier' => "security_xss_attempt:{$this->clientIp},{$this->field}",
                'category' => 'input_validation',
                'requires_alert' => true, // XSS-Versuche erfordern immer Alerts
            ])
            ->withDebug([
                'sanitized_value' => $this->sanitizeValueForLog($this->originalValue),
            ])
            ->withMetadata([
                'security_event' => true,
                'owasp_compliant' => true,
                'log_level' => 'ERROR',
                'attack_type' => 'xss',
                'critical_security_event' => true,
            ]);

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

    // === Factory Methods für verschiedene XSS-Patterns ===

    public static function scriptTag(string $clientIp, string $field, string $value): self
    {
        return new self($clientIp, $field, '<script> tag injection', $value, 'reflected');
    }

    public static function onEventHandler(string $clientIp, string $field, string $value): self
    {
        return new self($clientIp, $field, 'Event handler injection (onclick, onload, etc.)', $value, 'reflected');
    }

    public static function javascriptProtocol(string $clientIp, string $field, string $value): self
    {
        return new self($clientIp, $field, 'javascript: protocol injection', $value, 'reflected');
    }

    public static function htmlInjection(string $clientIp, string $field, string $value): self
    {
        return new self($clientIp, $field, 'HTML tag injection', $value, 'reflected');
    }

    public static function domBasedXss(string $clientIp, string $field, string $value): self
    {
        return new self($clientIp, $field, 'DOM-based XSS pattern', $value, 'dom');
    }

    public static function storedXss(string $clientIp, string $field, string $value): self
    {
        return new self($clientIp, $field, 'Stored XSS pattern', $value, 'stored');
    }

    public static function cssInjection(string $clientIp, string $field, string $value): self
    {
        return new self($clientIp, $field, 'CSS injection with expression()', $value, 'reflected');
    }

    public static function svgXss(string $clientIp, string $field, string $value): self
    {
        return new self($clientIp, $field, 'SVG-based XSS injection', $value, 'reflected');
    }

    /**
     * Gibt OWASP-konforme Event-Daten zurück
     */
    public function getSecurityEventData(): array
    {
        return [
            'event_identifier' => "security_xss_attempt:{$this->clientIp},{$this->field}",
            'description' => "Client {$this->clientIp} attempted XSS attack in field {$this->field}",
            'category' => 'input_validation',
            'log_level' => 'ERROR',
            'requires_alert' => true,
            'client_ip' => $this->clientIp,
            'field' => $this->field,
            'pattern' => $this->pattern,
            'xss_type' => $this->xssType,
            'attack_severity' => $this->getAttackSeverity(),
        ];
    }

    /**
     * Bestimmt Schweregrad des XSS-Angriffs
     */
    public function getAttackSeverity(): string
    {
        return match ($this->xssType) {
            'stored' => 'CRITICAL',  // Stored XSS ist am gefährlichsten
            'dom' => 'HIGH',         // DOM-based XSS ist schwer zu erkennen
            'reflected' => 'MEDIUM', // Reflected XSS ist häufig aber weniger persistent
            default => 'MEDIUM',
        };
    }

    /**
     * XSS-Versuche erfordern immer Alerts
     */
    public function requiresAlert(): bool
    {
        return true;
    }

    /**
     * Prüft ob es ein automatisierter Angriff ist
     */
    public function isAutomatedAttack(): bool
    {
        $automatedPatterns = [
            'alert(',
            'prompt(',
            'confirm(',
            'document.cookie',
            'xss',
            '<svg',
            'javascript:',
            'onerror=',
            'onload=',
        ];

        $lowerValue = strtolower($this->originalValue);
        foreach ($automatedPatterns as $pattern) {
            if (str_contains($lowerValue, $pattern)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Analysiert das XSS-Pattern detailliert
     */
    public function analyzePattern(): array
    {
        $analysis = [
            'vector_type' => 'unknown',
            'payload_complexity' => 'low',
            'encoding_used' => false,
            'obfuscation_detected' => false,
            'automation_detected' => $this->isAutomatedAttack(),
            'potential_impact' => 'low',
        ];

        $lowerValue = strtolower($this->originalValue);

        // Vector-Typ-Erkennung
        if (str_contains($lowerValue, '<script')) {
            $analysis['vector_type'] = 'script_tag';
        } elseif (str_contains($lowerValue, 'on') && preg_match('/on\w+\s*=/', $lowerValue)) {
            $analysis['vector_type'] = 'event_handler';
        } elseif (str_contains($lowerValue, 'javascript:')) {
            $analysis['vector_type'] = 'javascript_protocol';
        } elseif (str_contains($lowerValue, '<svg') || str_contains($lowerValue, '<embed')) {
            $analysis['vector_type'] = 'svg_embed';
        } elseif (str_contains($lowerValue, 'expression(')) {
            $analysis['vector_type'] = 'css_expression';
        }

        // Komplexität bewerten
        if (strlen($this->originalValue) > 100) {
            $analysis['payload_complexity'] = 'high';
        } elseif (strlen($this->originalValue) > 50) {
            $analysis['payload_complexity'] = 'medium';
        }

        // Encoding-Erkennung
        if (str_contains($this->originalValue, '%') || str_contains($this->originalValue, '&#')) {
            $analysis['encoding_used'] = true;
        }

        // Obfuskierung-Erkennung
        if (preg_match('/String\.fromCharCode|eval\(|unescape\(/', $this->originalValue)) {
            $analysis['obfuscation_detected'] = true;
            $analysis['payload_complexity'] = 'high';
        }

        // Impact-Bewertung
        if (str_contains($lowerValue, 'cookie') || str_contains($lowerValue, 'document')) {
            $analysis['potential_impact'] = 'high';
        } elseif (str_contains($lowerValue, 'alert') || str_contains($lowerValue, 'prompt')) {
            $analysis['potential_impact'] = 'medium';
        }

        return $analysis;
    }

    /**
     * Gibt benutzerfreundliche Fehlermeldung zurück (ohne Details zu verraten)
     */
    public function getUserMessage(): string
    {
        return "Invalid input detected. Please check your data and try again.";
    }

    /**
     * Bereinigt Wert für sicheres Logging
     */
    private function sanitizeValueForLog(string $value): string
    {
        // HTML-Entities kodieren und auf 100 Zeichen begrenzen
        $sanitized = htmlspecialchars(substr($value, 0, 100), ENT_QUOTES, 'UTF-8');

        // Zusätzliche Bereinigung für Logs
        $sanitized = preg_replace('/\s+/', ' ', $sanitized);

        return trim($sanitized);
    }

    /**
     * Gibt spezifische Verteidigungsempfehlung zurück
     */
    public function getDefenseRecommendation(): string
    {
        return match ($this->xssType) {
            'stored' => 'Implement output encoding, Content Security Policy, and input validation.',
            'dom' => 'Use safe JavaScript APIs, validate DOM manipulation, implement CSP.',
            'reflected' => 'Implement output encoding, input validation, and Content Security Policy.',
            default => 'Use output encoding, input validation, and Content Security Policy.',
        };
    }

    /**
     * Generiert Content Security Policy Empfehlungen
     */
    public function getCspRecommendations(): array
    {
        $baseCSP = [
            "default-src 'self'",
            "script-src 'self'",
            "style-src 'self' 'unsafe-inline'",
            "img-src 'self' data:",
            "connect-src 'self'",
            "font-src 'self'",
            "object-src 'none'",
            "frame-src 'none'",
        ];

        $analysis = $this->analyzePattern();

        // Verschärfungen basierend auf Angriffsmuster
        if ($analysis['vector_type'] === 'script_tag') {
            $baseCSP[] = "script-src 'self' 'nonce-{random}'"; // Nonce-basierte Scripts
        }

        if ($analysis['vector_type'] === 'css_expression') {
            $baseCSP[] = "style-src 'self'"; // Entferne unsafe-inline
        }

        return $baseCSP;
    }

    /**
     * Generiert IOC (Indicator of Compromise) für Security-Teams
     */
    public function generateIOC(): array
    {
        return [
            'type' => 'xss_attempt',
            'source_ip' => $this->clientIp,
            'target_field' => $this->field,
            'pattern' => $this->pattern,
            'xss_type' => $this->xssType,
            'severity' => $this->getAttackSeverity(),
            'timestamp' => date('Y-m-d H:i:s'),
            'automated' => $this->isAutomatedAttack(),
            'analysis' => $this->analyzePattern(),
        ];
    }

    /**
     * Prüft ob sofortige Gegenmaßnahmen erforderlich sind
     */
    public function requiresImmediateAction(): bool
    {
        return $this->xssType === 'stored' ||
               $this->getAttackSeverity() === 'CRITICAL' ||
               $this->isAutomatedAttack();
    }

    /**
     * Gibt WAF (Web Application Firewall) Regel-Vorschläge zurück
     */
    public function getWafRuleSuggestions(): array
    {
        $rules = [];

        $analysis = $this->analyzePattern();

        switch ($analysis['vector_type']) {
            case 'script_tag':
                $rules[] = 'Block requests containing <script tags in input fields';

                break;
            case 'event_handler':
                $rules[] = 'Block requests containing on* event handlers in input fields';

                break;
            case 'javascript_protocol':
                $rules[] = 'Block requests containing javascript: protocol in input fields';

                break;
            case 'svg_embed':
                $rules[] = 'Block or sanitize SVG/embed tags in user input';

                break;
        }

        if ($analysis['encoding_used']) {
            $rules[] = 'Implement URL decoding before XSS detection';
        }

        if ($analysis['obfuscation_detected']) {
            $rules[] = 'Implement advanced obfuscation detection rules';
        }

        return $rules;
    }
}
