<?php

declare(strict_types=1);

namespace App\Framework\Waf\Analysis\Analyzers;

use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Waf\Analysis\AnalysisResult;
use App\Framework\Waf\Analysis\AnalysisType;
use App\Framework\Waf\Analysis\ValueObjects\RequestAnalysisData;
use App\Framework\Waf\DetectionSeverity;

/**
 * Request Body Analyzer
 * Analyzes request body content for security threats
 */
final class BodyAnalyzer implements AnalyzerInterface
{
    public function __construct(
        private readonly bool $enabled = true,
        private readonly int $maxBodySize = 10485760, // 10MB
        private readonly int $maxScanSize = 1048576,  // 1MB for deep analysis
        private readonly bool $enableDeepScanning = true,
        private readonly array $suspiciousPatterns = [
            // SQL Injection patterns
            'sql_injection' => [
                '/(?i:union[\s\/\*]+(?:all[\s\/\*]+)?select)/',
                '/(?i:[\'\"`][\s]*(?:or|and)[\s]*[\'\"`]*[\s]*(?:[\'\"`]*[\w]+[\'\"`]*[\s]*=[\s]*[\'\"`]*[\w]+|[\d]+[\s]*=[\s]*[\d]+))/',
                '/(?i:(?:--|#|\/\*|\*\/))/',
            ],
            // XSS patterns
            'xss' => [
                '/(?i:<script[^>]*>.*?<\/script>)/',
                '/(?i:on(?:load|error|click|focus|blur|change|submit|reset|select|keydown|keyup|keypress|mouseover|mouseout|mousedown|mouseup|mousemove)[\s]*=)/',
                '/(?i:javascript[\s]*:)/',
            ],
            // Command injection
            'command_injection' => [
                '/(?i:(?:;|\||\|\||&&|&|`|\$\(|\${)[\s]*(?:cat|ls|pwd|id|uname|whoami|ps|kill|rm|mv|cp|chmod|chown|find|grep|awk|sed|sort|head|tail|wc|netstat|ifconfig|ping|wget|curl|nc|telnet|ssh|su|sudo|passwd|shadow|etc\/passwd|etc\/shadow|proc\/))/',
                '/(?i:(?:cmd|command)\.exe|powershell|bash|sh|zsh|csh|tcsh|fish)[\s]*(?:\/c|\/k|-c|-e)/',
            ],
            // Path traversal
            'path_traversal' => [
                '/(?i:(?:\.\.[\\/])|(?:[\\/]\.\.)|(?:\.\.\\\\)|(?:\\\\\.\.)|(?:%2e%2e%2f)|(?:%2e%2e\\\\)|(?:\.\.%2f)|(?:\.\.%5c)|(?:%2e%2e%5c)|(?:%c0%ae%c0%ae%c0%af)|(?:%c1%9c%c1%9c%c1%af))/',
                '/(?i:(?:\/etc\/passwd|\/etc\/shadow|\/etc\/hosts|\/proc\/|\/sys\/|c:[\\\\\\/]|\\\\\\\\))/',
            ],
            // PHP code injection
            'php_injection' => [
                '/(?i:(?:<\?(?:php)?|<\?=|\?>)|(?:eval|assert|create_function|call_user_func|call_user_func_array|preg_replace|system|exec|shell_exec|passthru|file_get_contents|file_put_contents|fopen|fwrite|include|require|include_once|require_once)[\s]*\(|(?:\$_(?:GET|POST|REQUEST|COOKIE|SESSION|SERVER|ENV)\[))/',
            ],
            // LDAP injection
            'ldap_injection' => [
                '/(?i:(?:\()(?:&|\|)(?:\(|\)))/',
            ],
            // XML/XXE attacks
            'xml_attacks' => [
                '/(?i:<!ENTITY[\s]+[\w]+[\s]+SYSTEM[\s]+["\'][^"\']*["\'])/',
                '/(?i:<!DOCTYPE[\s]+[\w]+[\s]*\[[\s]*<!ENTITY)/',
            ],
        ]
    ) {
    }

    public function getType(): AnalysisType
    {
        return AnalysisType::BODY;
    }

    public function canAnalyze(RequestAnalysisData $requestData): bool
    {
        return $requestData->hasBody();
    }

    public function analyze(RequestAnalysisData $requestData): AnalysisResult
    {
        $startTime = Timestamp::now();
        $findings = [];
        $metadata = [];
        $severity = DetectionSeverity::INFO;
        $message = 'Body analysis completed';

        $body = $requestData->body;
        $bodySize = strlen($body);

        $metadata['body_size'] = $bodySize;
        $metadata['content_type'] = $requestData->contentType;

        // Check body size limits
        if ($bodySize > $this->maxBodySize) {
            $findings[] = [
                'type' => 'oversized_body',
                'message' => "Request body too large: " . Byte::fromBytes($bodySize)->toHumanReadable(),
                'severity' => 'high',
                'size' => $bodySize,
                'limit' => $this->maxBodySize,
            ];
            $severity = DetectionSeverity::HIGH;
            $message = 'Request body exceeds size limits';
        }

        // Skip deep analysis if body is too large
        if ($bodySize > $this->maxScanSize) {
            $metadata['deep_scan_skipped'] = true;
            $metadata['scan_reason'] = 'Body too large for deep analysis';
        } elseif ($this->enableDeepScanning) {
            // Perform deep content analysis
            $deepScanResults = $this->performDeepScan($body);
            $findings = array_merge($findings, $deepScanResults['findings']);
            $metadata = array_merge($metadata, $deepScanResults['metadata']);

            if (! empty($deepScanResults['findings'])) {
                $maxSeverity = $this->getMaxSeverityFromFindings($deepScanResults['findings']);
                if ($maxSeverity->getCvssScore() > $severity->getCvssScore()) {
                    $severity = $maxSeverity;
                }
            }
        }

        // Check for suspicious encoding
        $encodingResults = $this->analyzeEncoding($body);
        if (! empty($encodingResults['findings'])) {
            $findings = array_merge($findings, $encodingResults['findings']);
            $metadata = array_merge($metadata, $encodingResults['metadata']);
        }

        // Check for binary content in text requests
        if ($this->isBinaryContent($body) && $this->isTextContentType($requestData->contentType)) {
            $findings[] = [
                'type' => 'binary_in_text',
                'message' => 'Binary content detected in text-based request',
                'severity' => 'medium',
                'content_type' => $requestData->contentType,
            ];
            if ($severity === DetectionSeverity::INFO || $severity === DetectionSeverity::LOW) {
                $severity = DetectionSeverity::MEDIUM;
            }
        }

        // Check for null bytes
        if (strpos($body, "\x00") !== false) {
            $findings[] = [
                'type' => 'null_byte_injection',
                'message' => 'Null byte detected in request body',
                'severity' => 'high',
                'count' => substr_count($body, "\x00"),
            ];
            $severity = DetectionSeverity::HIGH;
        }

        // Check for extremely long lines (potential buffer overflow attempts)
        $lines = explode("\n", $body);
        $maxLineLength = 0;
        foreach ($lines as $line) {
            $lineLength = strlen($line);
            if ($lineLength > $maxLineLength) {
                $maxLineLength = $lineLength;
            }
        }

        if ($maxLineLength > 8192) {
            $findings[] = [
                'type' => 'long_line',
                'message' => "Extremely long line detected: {$maxLineLength} characters",
                'severity' => 'medium',
                'length' => $maxLineLength,
            ];
            if ($severity === DetectionSeverity::INFO || $severity === DetectionSeverity::LOW) {
                $severity = DetectionSeverity::MEDIUM;
            }
        }

        $endTime = Timestamp::now();
        $processingTime = $startTime->diff($endTime);

        $passed = empty($findings) ||
                  ! in_array($severity, [DetectionSeverity::HIGH, DetectionSeverity::CRITICAL]);

        if (! $passed) {
            $criticalFindings = array_filter($findings, fn ($f) => $f['severity'] === 'critical');
            $highFindings = array_filter($findings, fn ($f) => $f['severity'] === 'high');

            if (! empty($criticalFindings)) {
                $message = count($criticalFindings) === 1
                    ? $criticalFindings[0]['message']
                    : "Critical security threats detected in request body (" . count($criticalFindings) . " threats)";
            } elseif (! empty($highFindings)) {
                $message = count($highFindings) === 1
                    ? $highFindings[0]['message']
                    : "High-severity security issues detected in request body (" . count($highFindings) . " issues)";
            } else {
                $message = "Security issues detected in request body (" . count($findings) . " findings)";
            }
        }

        $metadata['analysis_duration_ms'] = $processingTime->toMilliseconds();
        $metadata['analyzer'] = 'BodyAnalyzer';
        $metadata['line_count'] = count($lines);
        $metadata['max_line_length'] = $maxLineLength;

        return (new AnalysisResult(
            type: $this->getType(),
            passed: $passed,
            severity: $severity,
            message: $message,
            findings: $findings,
            metadata: $metadata
        ))->withProcessingTime($processingTime);
    }

    /**
     * Perform deep content scanning for attack patterns
     */
    private function performDeepScan(string $body): array
    {
        $findings = [];
        $metadata = [];
        $patternsMatched = 0;

        foreach ($this->suspiciousPatterns as $category => $patterns) {
            $categoryMatches = 0;

            foreach ($patterns as $pattern) {
                if (preg_match($pattern, $body, $matches, PREG_OFFSET_CAPTURE)) {
                    $categoryMatches++;
                    $patternsMatched++;

                    $matchText = $matches[0][0];
                    $matchOffset = $matches[0][1];

                    $severity = $this->getSeverityForCategory($category);

                    $findings[] = [
                        'type' => $category,
                        'message' => "Suspicious {$category} pattern detected",
                        'severity' => $severity,
                        'pattern' => $pattern,
                        'match' => substr($matchText, 0, 100), // Truncate for safety
                        'offset' => $matchOffset,
                        'category' => $category,
                    ];
                }
            }

            if ($categoryMatches > 0) {
                $metadata["matches_{$category}"] = $categoryMatches;
            }
        }

        $metadata['deep_scan_performed'] = true;
        $metadata['patterns_checked'] = array_sum(array_map('count', $this->suspiciousPatterns));
        $metadata['patterns_matched'] = $patternsMatched;

        return [
            'findings' => $findings,
            'metadata' => $metadata,
        ];
    }

    /**
     * Analyze content encoding and detect evasion attempts
     */
    private function analyzeEncoding(string $body): array
    {
        $findings = [];
        $metadata = [];

        // Check for URL encoding evasion
        $urlEncodedChars = preg_match_all('/%[0-9a-fA-F]{2}/', $body);
        if ($urlEncodedChars > 50) {
            $findings[] = [
                'type' => 'excessive_url_encoding',
                'message' => "Excessive URL encoding detected: {$urlEncodedChars} encoded characters",
                'severity' => 'medium',
                'count' => $urlEncodedChars,
            ];
        }

        // Check for Unicode evasion
        $unicodeChars = preg_match_all('/(?:%u[0-9a-fA-F]{4}|\\\\u[0-9a-fA-F]{4})/', $body);
        if ($unicodeChars > 0) {
            $findings[] = [
                'type' => 'unicode_evasion',
                'message' => "Unicode evasion attempt detected: {$unicodeChars} Unicode sequences",
                'severity' => 'medium',
                'count' => $unicodeChars,
            ];
        }

        // Check for double encoding
        if (preg_match('/%25[0-9a-fA-F]{2}/', $body)) {
            $findings[] = [
                'type' => 'double_encoding',
                'message' => 'Double URL encoding detected (potential evasion)',
                'severity' => 'high',
            ];
        }

        $metadata['url_encoded_chars'] = $urlEncodedChars;
        $metadata['unicode_chars'] = $unicodeChars;

        return [
            'findings' => $findings,
            'metadata' => $metadata,
        ];
    }

    /**
     * Check if content is binary
     */
    private function isBinaryContent(string $content): bool
    {
        // Check for common binary indicators
        $binaryChars = ["\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08"];

        foreach ($binaryChars as $char) {
            if (strpos($content, $char) !== false) {
                return true;
            }
        }

        // Check for high ratio of non-printable characters
        $printableChars = 0;
        $totalChars = strlen($content);

        if ($totalChars === 0) {
            return false;
        }

        for ($i = 0; $i < min($totalChars, 1000); $i++) {
            $char = ord($content[$i]);
            if (($char >= 32 && $char <= 126) || $char === 9 || $char === 10 || $char === 13) {
                $printableChars++;
            }
        }

        $printableRatio = $printableChars / min($totalChars, 1000);

        return $printableRatio < 0.85; // Less than 85% printable = likely binary
    }

    /**
     * Check if content type is text-based
     */
    private function isTextContentType(?string $contentType): bool
    {
        if ($contentType === null) {
            return true; // Assume text if no content type
        }

        $textTypes = [
            'text/',
            'application/json',
            'application/xml',
            'application/x-www-form-urlencoded',
        ];

        $contentType = strtolower($contentType);

        foreach ($textTypes as $type) {
            if (str_starts_with($contentType, $type)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get severity level for attack category
     */
    private function getSeverityForCategory(string $category): string
    {
        return match ($category) {
            'sql_injection', 'command_injection', 'php_injection' => 'critical',
            'xss', 'path_traversal', 'xml_attacks' => 'high',
            'ldap_injection' => 'medium',
            default => 'medium'
        };
    }

    /**
     * Get maximum severity from findings
     */
    private function getMaxSeverityFromFindings(array $findings): DetectionSeverity
    {
        $maxSeverity = DetectionSeverity::INFO;

        foreach ($findings as $finding) {
            $severity = match ($finding['severity']) {
                'critical' => DetectionSeverity::CRITICAL,
                'high' => DetectionSeverity::HIGH,
                'medium' => DetectionSeverity::MEDIUM,
                'low' => DetectionSeverity::LOW,
                default => DetectionSeverity::INFO
            };

            if ($severity->getCvssScore() > $maxSeverity->getCvssScore()) {
                $maxSeverity = $severity;
            }
        }

        return $maxSeverity;
    }

    public function getConfiguration(): array
    {
        return [
            'enabled' => $this->enabled,
            'max_body_size' => $this->maxBodySize,
            'max_scan_size' => $this->maxScanSize,
            'enable_deep_scanning' => $this->enableDeepScanning,
            'pattern_categories' => array_keys($this->suspiciousPatterns),
            'total_patterns' => array_sum(array_map('count', $this->suspiciousPatterns)),
        ];
    }

    public function isEnabled(): bool
    {
        return $this->enabled;
    }

    public function getPriority(): int
    {
        return $this->getType()->getPriority();
    }

    public function getExpectedProcessingTime(): int
    {
        return $this->getType()->getMaxProcessingTime();
    }

    public function supportsParallelExecution(): bool
    {
        return $this->getType()->canRunInParallel();
    }

    public function getDependencies(): array
    {
        return $this->getType()->getDependencies();
    }
}
