<?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 Path-Traversal-Angriffs-Versuche
 *
 * Verwendet OWASP-konforme Nachrichten für Path-Traversal-Detection
 */
final class PathTraversalAttemptException extends FrameworkException
{
    /**
     * @param string $clientIp Client-IP für Security-Tracking
     * @param string $requestedPath Angeforderte Pfad
     * @param string $pattern Erkanntes Path-Traversal-Pattern
     * @param string $attackContext Kontext (file_access, url_parameter, etc.)
     * @param \Throwable|null $previous Vorherige Ausnahme
     */
    public function __construct(
        public readonly string $clientIp,
        public readonly string $requestedPath,
        public readonly string $pattern,
        public readonly string $attackContext = 'file_access',
        ?\Throwable $previous = null
    ) {
        // OWASP-konforme Nachricht mit Platzhaltern
        $message = "Client {$this->clientIp} attempted path traversal: {$this->requestedPath}";

        $context = ExceptionContext::forOperation('security.path_traversal_detection', 'Security')
            ->withData([
                'client_ip' => $this->clientIp,
                'requested_path' => $this->requestedPath,
                'pattern' => $this->pattern,
                'context' => $this->attackContext,
                'path_depth' => $this->calculateTraversalDepth(),
                'event_identifier' => "security_path_traversal:{$this->clientIp}",
                'category' => 'file_access',
                'requires_alert' => true, // Path-Traversal-Versuche erfordern immer Alerts
            ])
            ->withMetadata([
                'security_event' => true,
                'owasp_compliant' => true,
                'log_level' => 'ERROR',
                'attack_type' => 'path_traversal',
                'critical_security_event' => true,
            ]);

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

    // === Factory Methods für verschiedene Path-Traversal-Patterns ===

    public static function dotDotSlash(string $clientIp, string $path): self
    {
        return new self($clientIp, $path, '../ pattern', 'file_access');
    }

    public static function dotDotBackslash(string $clientIp, string $path): self
    {
        return new self($clientIp, $path, '..\ pattern', 'file_access');
    }

    public static function encodedTraversal(string $clientIp, string $path): self
    {
        return new self($clientIp, $path, 'URL-encoded traversal', 'file_access');
    }

    public static function unicodeTraversal(string $clientIp, string $path): self
    {
        return new self($clientIp, $path, 'Unicode-encoded traversal', 'file_access');
    }

    public static function absolutePath(string $clientIp, string $path): self
    {
        return new self($clientIp, $path, 'Absolute path access', 'file_access');
    }

    public static function systemPath(string $clientIp, string $path): self
    {
        return new self($clientIp, $path, 'System directory access', 'system_access');
    }

    public static function configFileAccess(string $clientIp, string $path): self
    {
        return new self($clientIp, $path, 'Configuration file access', 'config_access');
    }

    /**
     * Gibt OWASP-konforme Event-Daten zurück
     */
    public function getSecurityEventData(): array
    {
        return [
            'event_identifier' => "security_path_traversal:{$this->clientIp}",
            'description' => "Client {$this->clientIp} attempted path traversal: {$this->requestedPath}",
            'category' => 'file_access',
            'log_level' => 'ERROR',
            'requires_alert' => true,
            'client_ip' => $this->clientIp,
            'requested_path' => $this->requestedPath,
            'pattern' => $this->pattern,
            'context' => $this->attackContext,
            'attack_severity' => $this->getAttackSeverity(),
        ];
    }

    /**
     * Bestimmt Schweregrad des Path-Traversal-Angriffs
     */
    public function getAttackSeverity(): string
    {
        // Systemverzeichnisse sind kritisch
        $criticalPaths = ['/etc/', '/var/', '/usr/', '/sys/', '/proc/', 'C:\Windows', 'C:\System'];
        foreach ($criticalPaths as $critical) {
            if (str_contains($this->requestedPath, $critical)) {
                return 'CRITICAL';
            }
        }

        // Konfigurationsdateien sind hochriskant
        $sensitiveFiles = ['.env', 'config', 'passwd', 'shadow', 'hosts', 'web.config'];
        foreach ($sensitiveFiles as $sensitive) {
            if (str_contains($this->requestedPath, $sensitive)) {
                return 'HIGH';
            }
        }

        // Deep traversal ist verdächtig
        if ($this->calculateTraversalDepth() > 3) {
            return 'HIGH';
        }

        return 'MEDIUM';
    }

    /**
     * Berechnet die Tiefe des Traversal-Versuchs
     */
    public function calculateTraversalDepth(): int
    {
        return substr_count($this->requestedPath, '../') + substr_count($this->requestedPath, '..\\');
    }

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

    /**
     * Prüft ob es ein automatisierter Angriff ist
     */
    public function isAutomatedAttack(): bool
    {
        // Typische automatisierte Scanner-Patterns
        $automatedPatterns = [
            '../../../../../../../../etc/passwd',
            '..\\..\\..\\..\\windows\\system32',
            '%2e%2e%2f', // URL-encoded ../
            '\x2e\x2e\x2f', // Hex-encoded
        ];

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

        return false;
    }

    /**
     * Analysiert das Path-Traversal-Pattern detailliert
     */
    public function analyzePattern(): array
    {
        $analysis = [
            'encoding_type' => 'none',
            'target_os' => 'unknown',
            'target_files' => [],
            'traversal_depth' => $this->calculateTraversalDepth(),
            'automation_detected' => $this->isAutomatedAttack(),
            'evasion_techniques' => [],
        ];

        // Encoding-Erkennung
        if (str_contains($this->requestedPath, '%')) {
            $analysis['encoding_type'] = 'url_encoded';
        } elseif (str_contains($this->requestedPath, '\x')) {
            $analysis['encoding_type'] = 'hex_encoded';
        } elseif (preg_match('/\\u[0-9a-f]{4}/i', $this->requestedPath)) {
            $analysis['encoding_type'] = 'unicode_encoded';
        }

        // OS-Erkennung
        if (str_contains($this->requestedPath, '\\')) {
            $analysis['target_os'] = 'windows';
        } elseif (str_contains($this->requestedPath, '/')) {
            $analysis['target_os'] = 'unix';
        }

        // Zieldateien identifizieren
        $targetFiles = [
            'passwd' => 'unix_password_file',
            'shadow' => 'unix_shadow_file',
            'hosts' => 'hosts_file',
            '.env' => 'environment_file',
            'config' => 'configuration_file',
            'web.config' => 'iis_config',
            'httpd.conf' => 'apache_config',
            'nginx.conf' => 'nginx_config',
        ];

        foreach ($targetFiles as $file => $description) {
            if (str_contains(strtolower($this->requestedPath), $file)) {
                $analysis['target_files'][] = $description;
            }
        }

        // Evasion-Techniken
        if (str_contains($this->requestedPath, './')) {
            $analysis['evasion_techniques'][] = 'current_directory_reference';
        }

        if (preg_match('/\.{3,}/', $this->requestedPath)) {
            $analysis['evasion_techniques'][] = 'multiple_dots';
        }

        if (str_contains($this->requestedPath, '//')) {
            $analysis['evasion_techniques'][] = 'double_slash';
        }

        return $analysis;
    }

    /**
     * Gibt benutzerfreundliche Fehlermeldung zurück (ohne Details zu verraten)
     */
    public function getUserMessage(): string
    {
        return "Invalid file path. Access denied.";
    }

    /**
     * Gibt spezifische Verteidigungsempfehlung zurück
     */
    public function getDefenseRecommendation(): string
    {
        return match ($this->attackContext) {
            'file_access' => 'Implement proper path validation, use allowlists, and restrict file access to specific directories.',
            'system_access' => 'Block access to system directories, implement strict path validation, and use security contexts.',
            'config_access' => 'Secure configuration files, implement access controls, and use environment variables.',
            default => 'Implement comprehensive path validation and access controls.',
        };
    }

    /**
     * Generiert IOC (Indicator of Compromise) für Security-Teams
     */
    public function generateIOC(): array
    {
        return [
            'type' => 'path_traversal_attempt',
            'source_ip' => $this->clientIp,
            'requested_path' => $this->requestedPath,
            'pattern' => $this->pattern,
            'context' => $this->attackContext,
            '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 in_array($this->getAttackSeverity(), ['HIGH', 'CRITICAL']) ||
               $this->isAutomatedAttack() ||
               $this->calculateTraversalDepth() > 5;
    }

    /**
     * Gibt WAF-Regel-Vorschläge zurück
     */
    public function getWafRuleSuggestions(): array
    {
        $rules = [
            'Block requests containing ../ or ..\\ patterns',
            'Block requests with URL-encoded path traversal sequences',
            'Block access to sensitive file extensions (.env, .config, etc.)',
        ];

        $analysis = $this->analyzePattern();

        if ($analysis['encoding_type'] !== 'none') {
            $rules[] = 'Implement URL decoding before path traversal detection';
        }

        if (! empty($analysis['target_files'])) {
            $rules[] = 'Block access to system configuration files and password files';
        }

        if ($analysis['traversal_depth'] > 3) {
            $rules[] = 'Limit maximum directory traversal depth';
        }

        return $rules;
    }

    /**
     * Gibt sichere Alternative für File-Access vor
     */
    public function getSecureAlternatives(): array
    {
        return [
            'Use file IDs instead of file paths in URLs',
            'Implement a file mapping table',
            'Restrict file access to a specific directory',
            'Use symbolic links instead of direct paths',
            'Implement role-based file access controls',
            'Validate file paths against an allowlist',
        ];
    }
}
