<?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;

/**
 * Path Traversal Detection Layer
 *
 * Detects directory traversal and path manipulation attempts.
 * Protects against unauthorized file access and system disclosure.
 */
final class PathTraversalLayer implements LayerInterface
{
    private LayerConfig $config;

    private LayerMetrics $metrics;

    private bool $enabled = true;

    /** Path traversal patterns */
    private const PATH_TRAVERSAL_PATTERNS = [
        // Classic directory traversal
        '/\.\.\//',
        '/\.\.\\\/',
        '/\.\.\\\\/',

        // Encoded versions
        '/%2e%2e%2f/',
        '/%2e%2e%5c/',
        '/%252e%252e%252f/',
        '/\.\.\%2f/',
        '/\.\.\%5c/',

        // Unicode encoded (using hex notation instead of \u)
        '/\x{002e}\x{002e}\x{002f}/u',
        '/\x{ff0e}\x{ff0e}\x{ff0f}/u',

        // Alternative representations
        '/\.\.%252f/',
        '/\.\.%c0%af/',
        '/\.\.%c1%9c/',

        // Null byte injection
        '/\.\.\/.*\x00/',
        '/\.\.\\\\.*\x00/',

        // System file access attempts
        '/\/etc\/passwd/',
        '/\/etc\/shadow/',
        '/\/etc\/hosts/',
        '/\/proc\/version/',
        '/\/proc\/self\/environ/',
        '/\/windows\/system32\//',
        '/\/winnt\/system32\//',

        // Common sensitive files
        '/\/etc\/.*/',
        '/\/var\/log\/.*/',
        '/\/root\/.*/',
        '/\/home\/.*\/\.\w+/',

        // Web application files
        '/\/web\.config/',
        '/\/app\.config/',
        '/\/\.htaccess/',
        '/\/\.htpasswd/',
        '/\/wp-config\.php/',
        '/\/configuration\.php/',
        '/\/config\.inc\.php/',

        // Backup and temporary files
        '/.*\.bak$/',
        '/.*\.backup$/',
        '/.*\.old$/',
        '/.*\.tmp$/',
        '/.*~$/',
        '/.*\.swp$/',

        // Source code disclosure
        '/.*\.php\.bak/',
        '/.*\.asp\.old/',
        '/.*\.jsp\.tmp/',
    ];

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

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

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

    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 request path (very important for path traversal)
            $detection = $this->analyzeString($request->path, 'request path');
            if ($detection) {
                $detections[] = $detection;
            }

            // Check specific headers that might contain file paths
            $pathHeaders = ['Referer', 'X-Original-URL', 'X-Rewrite-URL'];
            foreach ($pathHeaders as $headerName) {
                $headerValue = $request->headers->getFirst($headerName);
                if ($headerValue) {
                    $detection = $this->analyzeString($headerValue, "header '{$headerName}'");
                    if ($detection) {
                        $detections[] = $detection;
                    }
                }
            }

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

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

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

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

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

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

        // Multiple decoding passes to catch encoded attempts
        $input = urldecode($input);
        $input = urldecode($input); // Second pass for double encoding

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

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

                return new Detection(
                    category: DetectionCategory::PATH_TRAVERSAL,
                    severity: $severity,
                    message: "Path traversal 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),
                        'threat_type' => $this->identifyThreatType($pattern),
                    ],
                    confidence: 0.9,
                    riskScore: $riskScore
                );
            }
        }

        return null;
    }

    /**
     * Calculate severity based on pattern and context
     */
    private function calculateSeverity(string $pattern, string $match): DetectionSeverity
    {
        // Critical - System file access
        if (preg_match('/\/(etc|proc|root|windows|winnt)\//', $pattern)) {
            return DetectionSeverity::CRITICAL;
        }

        // Critical - Sensitive application files
        if (preg_match('/(passwd|shadow|config|htaccess|htpasswd)/', $pattern)) {
            return DetectionSeverity::CRITICAL;
        }

        // High - Directory traversal with sensitive extensions
        if (preg_match('/\.\.(\/|\\\\).*\.(php|asp|jsp|config)/', $match)) {
            return DetectionSeverity::HIGH;
        }

        // High - Multiple directory traversals
        if (substr_count($match, '..') > 2) {
            return DetectionSeverity::HIGH;
        }

        // Medium - Basic directory traversal
        return DetectionSeverity::MEDIUM;
    }

    /**
     * Calculate risk score based on pattern and location
     */
    private function calculateRiskScore(string $pattern, string $location): float
    {
        $baseScore = 70.0;

        // Higher risk in request path
        if ($location === 'request path') {
            $baseScore += 15.0;
        }

        // System files are highest risk
        if (preg_match('/\/(etc|proc|root|windows)\//', $pattern)) {
            $baseScore += 20.0;
        }

        // Config files are high risk
        if (preg_match('/(config|htaccess|passwd)/', $pattern)) {
            $baseScore += 15.0;
        }

        // Encoded attempts show deliberate evasion
        if (preg_match('/%[0-9a-f]{2}/', $pattern)) {
            $baseScore += 10.0;
        }

        return min(100.0, $baseScore);
    }

    /**
     * Identify the type of threat based on pattern
     */
    private function identifyThreatType(string $pattern): string
    {
        if (preg_match('/\/(etc|proc)\//', $pattern)) {
            return 'system_file_access';
        }

        if (preg_match('/(config|htaccess|passwd)/', $pattern)) {
            return 'configuration_disclosure';
        }

        if (preg_match('/\.(bak|backup|old|tmp)/', $pattern)) {
            return 'backup_file_access';
        }

        if (preg_match('/\.\./', $pattern)) {
            return 'directory_traversal';
        }

        return 'file_access_attempt';
    }

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

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

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

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

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

    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::PATH_TRAVERSAL, DetectionCategory::BROKEN_ACCESS_CONTROL];
    }
}
