<?php

declare(strict_types=1);

namespace App\Framework\Waf\Layers;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Http\Request;
use App\Framework\Waf\DetectionCategory;
use App\Framework\Waf\DetectionSeverity;
use App\Framework\Waf\LayerResult;
use App\Framework\Waf\LayerStatus;
use App\Framework\Waf\ValueObjects\Detection;
use App\Framework\Waf\ValueObjects\LayerConfig;
use App\Framework\Waf\ValueObjects\LayerMetrics;

/**
 * Command Injection Detection Layer
 *
 * Detects operating system command injection attempts.
 * Protects against arbitrary command execution vulnerabilities.
 */
final class CommandInjectionLayer implements LayerInterface
{
    private LayerConfig $config;

    private LayerMetrics $metrics;

    private bool $enabled = true;

    /** Command injection patterns */
    private const COMMAND_INJECTION_PATTERNS = [
        // Command separators
        '/[;&|`]+/',
        '/\|\|/',
        '/&&/',

        // Common Unix/Linux commands
        '/\b(ls|cat|pwd|whoami|id|uname|ps|netstat|ifconfig|mount)\b/i',
        '/\b(grep|find|locate|which|whereis|file|head|tail|more|less)\b/i',
        '/\b(chmod|chown|mkdir|rmdir|rm|cp|mv|ln|touch)\b/i',
        '/\b(wget|curl|nc|netcat|telnet|ssh|ftp|tftp)\b/i',
        '/\b(su|sudo|passwd|useradd|userdel|usermod)\b/i',

        // Common Windows commands
        '/\b(dir|type|copy|del|move|md|rd|cd|cls|ver)\b/i',
        '/\b(net|ipconfig|ping|tracert|nslookup|arp|route)\b/i',
        '/\b(tasklist|taskkill|sc|reg|wmic|powershell|cmd)\b/i',
        '/\b(systeminfo|whoami|echo|set|path)\b/i',

        // System information and process commands
        '/\b(ps|top|htop|kill|killall|pkill|nohup|jobs|bg|fg)\b/i',
        '/\b(crontab|at|service|systemctl|chkconfig|update-rc\.d)\b/i',
        '/\b(iptables|netfilter|firewall|selinux|apparmor)\b/i',

        // File operations and text processing
        '/\b(awk|sed|sort|uniq|wc|diff|patch|tar|gzip|gunzip)\b/i',
        '/\b(zip|unzip|compress|uncompress|ar|strings|od|hexdump)\b/i',

        // Network and system utilities
        '/\b(dig|nslookup|host|whois|traceroute|mtr|tcpdump|wireshark)\b/i',
        '/\b(lsof|strace|ltrace|gdb|objdump|readelf|nm)\b/i',

        // Dangerous system calls
        '/\b(exec|system|shell_exec|passthru|eval|popen|proc_open)\b/i',
        '/\b(Runtime\.getRuntime|ProcessBuilder|cmd\.exe|sh)\b/i',

        // Command substitution
        '/\$\(.*\)/',
        '/`[^`]*`/',
        '/\${.*}/',

        // Input/Output redirection
        '/[<>]+/',
        '/\b(tee|xargs)\b/i',

        // Environment variable manipulation
        '/\$\w+/',
        '/\bexport\s+\w+=/i',
        '/\bset\s+\w+=/i',

        // Script interpreters
        '/\b(bash|sh|csh|tcsh|zsh|fish|ksh|dash)\b/i',
        '/\b(python|perl|ruby|php|node|java|lua)\b/i',

        // Encoded command attempts
        '/%5C/', // Backslash
        '/%7C/', // Pipe
        '/%26/', // Ampersand
        '/%3B/', // Semicolon
        '/%60/', // Backtick

        // Null byte injection for command termination
        '/\x00/',
        '/%00/',
    ];

    public function __construct()
    {
        $this->config = new LayerConfig(
            enabled: true,
            timeout: Duration::fromMilliseconds(40),
            confidenceThreshold: Percentage::from(92.0),
            blockingMode: true,
            logDetections: true,
            maxDetectionsPerRequest: 5
        );

        $this->metrics = new LayerMetrics();
    }

    public function getName(): string
    {
        return 'command_injection';
    }

    public function analyze(Request $request): LayerResult
    {
        $startTime = microtime(true);
        $detections = [];

        try {
            // Check query parameters
            foreach ($request->queryParams as $key => $value) {
                if (is_string($value)) {
                    $detection = $this->analyzeString($value, "query parameter '{$key}'");
                    if ($detection) {
                        $detections[] = $detection;
                    }
                }
            }

            // Check POST data
            if (isset($request->parsedBody->data) && is_array($request->parsedBody->data)) {
                foreach ($request->parsedBody->data as $key => $value) {
                    if (is_string($value)) {
                        $detection = $this->analyzeString($value, "POST parameter '{$key}'");
                        if ($detection) {
                            $detections[] = $detection;
                        }
                    }
                }
            }

            // Check headers that commonly contain command injection attempts
            $suspiciousHeaders = ['User-Agent', 'Referer', 'X-Forwarded-For', 'Cookie', 'X-Real-IP'];
            foreach ($suspiciousHeaders as $headerName) {
                $headerValue = $request->headers->getFirst($headerName);
                if ($headerValue) {
                    $detection = $this->analyzeString($headerValue, "header '{$headerName}'");
                    if ($detection) {
                        $detections[] = $detection;
                    }
                }
            }

            // Check request path
            $detection = $this->analyzeString($request->path, 'request path');
            if ($detection) {
                $detections[] = $detection;
            }

            $processingTime = Duration::fromMilliseconds((microtime(true) - $startTime) * 1000);

            if (! empty($detections)) {
                return LayerResult::threat(
                    $this->getName(),
                    'Command injection attempt detected',
                    LayerStatus::THREAT_DETECTED,
                    $detections,
                    $processingTime
                );
            }

            return LayerResult::clean(
                $this->getName(),
                'No command injection patterns detected',
                $processingTime
            );

        } catch (\Throwable $e) {
            $processingTime = Duration::fromMilliseconds((microtime(true) - $startTime) * 1000);

            return LayerResult::error(
                $this->getName(),
                'Command injection analysis failed: ' . $e->getMessage(),
                $processingTime
            );
        }
    }

    /**
     * Analyze string for command injection patterns
     */
    private function analyzeString(string $input, string $location): ?Detection
    {
        $originalInput = $input;

        // Multiple decoding passes
        $input = urldecode($input);
        $input = html_entity_decode($input, ENT_QUOTES | ENT_HTML5);
        $input = urldecode($input); // Second pass for double encoding

        foreach (self::COMMAND_INJECTION_PATTERNS as $pattern) {
            if (preg_match($pattern, $input, $matches)) {

                $severity = $this->calculateSeverity($pattern, $matches[0] ?? '', $input);
                $riskScore = $this->calculateRiskScore($pattern, $location, $input);

                return new Detection(
                    category: DetectionCategory::COMMAND_INJECTION,
                    severity: $severity,
                    message: "Command injection pattern detected in {$location}",
                    details: [
                        'location' => $location,
                        'pattern' => $pattern,
                        'matched_text' => $matches[0] ?? '',
                        'input_length' => strlen($originalInput),
                        'decoded_input' => substr($input, 0, 200),
                        'original_input' => substr($originalInput, 0, 200),
                        'command_type' => $this->identifyCommandType($pattern, $matches[0] ?? ''),
                        'risk_factors' => $this->analyzeRiskFactors($input),
                    ],
                    confidence: 0.88,
                    riskScore: $riskScore
                );
            }
        }

        return null;
    }

    /**
     * Calculate severity based on pattern and context
     */
    private function calculateSeverity(string $pattern, string $match, string $fullInput): DetectionSeverity
    {
        // Critical - System administration commands
        if (preg_match('/\b(sudo|su|passwd|useradd|rm|chmod|chown)\b/i', $match)) {
            return DetectionSeverity::CRITICAL;
        }

        // Critical - Command execution functions
        if (preg_match('/\b(exec|system|shell_exec|eval)\b/i', $match)) {
            return DetectionSeverity::CRITICAL;
        }

        // Critical - Multiple command separators
        if (preg_match('/[;&|`]{2,}/', $match)) {
            return DetectionSeverity::CRITICAL;
        }

        // High - Network commands
        if (preg_match('/\b(wget|curl|nc|netcat|ssh|ftp)\b/i', $match)) {
            return DetectionSeverity::HIGH;
        }

        // High - Command substitution
        if (preg_match('/(\$\(|\`|`|\${)/', $pattern)) {
            return DetectionSeverity::HIGH;
        }

        // High - Process management
        if (preg_match('/\b(ps|kill|killall|top|htop)\b/i', $match)) {
            return DetectionSeverity::HIGH;
        }

        // Medium - File operations
        if (preg_match('/\b(cat|ls|find|grep|head|tail)\b/i', $match)) {
            return DetectionSeverity::MEDIUM;
        }

        // Medium - Single command separators
        if (preg_match('/[;&|]/', $match)) {
            return DetectionSeverity::MEDIUM;
        }

        return DetectionSeverity::LOW;
    }

    /**
     * Calculate risk score
     */
    private function calculateRiskScore(string $pattern, string $location, string $input): float
    {
        $baseScore = 75.0;

        // Location-based risk adjustment
        if ($location === 'request path') {
            $baseScore += 10.0;
        } elseif (str_contains($location, 'POST parameter')) {
            $baseScore += 5.0;
        }

        // Pattern-based risk adjustment
        if (preg_match('/\b(sudo|su|rm|passwd)\b/i', $input)) {
            $baseScore += 20.0;
        }

        if (preg_match('/[;&|`]{2,}/', $input)) {
            $baseScore += 15.0;
        }

        if (preg_match('/(\$\(|\`|`|\${)/', $input)) {
            $baseScore += 10.0;
        }

        // Multiple command indicators
        $commandCount = preg_match_all('/\b(ls|cat|pwd|whoami|wget|curl)\b/i', $input);
        if ($commandCount > 1) {
            $baseScore += ($commandCount * 5);
        }

        return min(100.0, $baseScore);
    }

    /**
     * Identify command type for classification
     */
    private function identifyCommandType(string $pattern, string $match): string
    {
        if (preg_match('/\b(ls|cat|pwd|find|grep)\b/i', $match)) {
            return 'file_operations';
        }

        if (preg_match('/\b(wget|curl|nc|ssh|ftp)\b/i', $match)) {
            return 'network_operations';
        }

        if (preg_match('/\b(ps|kill|top|htop|service)\b/i', $match)) {
            return 'process_management';
        }

        if (preg_match('/\b(sudo|su|passwd|useradd)\b/i', $match)) {
            return 'system_administration';
        }

        if (preg_match('/[;&|`]+/', $pattern)) {
            return 'command_chaining';
        }

        if (preg_match('/(\$\(|\`|\${)/', $pattern)) {
            return 'command_substitution';
        }

        return 'general_command';
    }

    /**
     * Analyze additional risk factors
     */
    private function analyzeRiskFactors(string $input): array
    {
        $factors = [];

        if (preg_match('/[;&|`]{2,}/', $input)) {
            $factors[] = 'multiple_command_separators';
        }

        if (preg_match('/\b(sudo|su)\b/i', $input)) {
            $factors[] = 'privilege_escalation';
        }

        if (preg_match('/(\$\w+|%\w+%)/', $input)) {
            $factors[] = 'environment_variables';
        }

        if (preg_match('/[<>]+/', $input)) {
            $factors[] = 'io_redirection';
        }

        if (preg_match('/%[0-9a-f]{2}/i', $input)) {
            $factors[] = 'encoded_content';
        }

        return $factors;
    }

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

    public function isHealthy(): bool
    {
        return true;
    }

    public function getPriority(): int
    {
        return $this->config->get('priority', 98);
    }

    public function getConfidenceLevel(): Percentage
    {
        return Percentage::from($this->config->get('confidence', 0.92) * 100);
    }

    public function getTimeoutThreshold(): Duration
    {
        return Duration::fromMilliseconds($this->config->get('timeout', 40));
    }

    public function configure(LayerConfig $config): void
    {
        $this->config = $config;
    }

    public function getConfig(): LayerConfig
    {
        return $this->config;
    }

    public function getMetrics(): LayerMetrics
    {
        return $this->metrics;
    }

    public function reset(): void
    {
        $this->metrics = new LayerMetrics();
    }

    public function warmUp(): void
    {
        // Pre-compile regex patterns if needed
    }

    public function shutdown(): void
    {
        // Cleanup resources
    }

    public function getDependencies(): array
    {
        return [];
    }

    public function supportsParallelProcessing(): bool
    {
        return true;
    }

    public function getVersion(): string
    {
        return '1.0.0';
    }

    public function getSupportedCategories(): array
    {
        return [DetectionCategory::COMMAND_INJECTION, DetectionCategory::INJECTION];
    }
}
