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

/**
 * SQL Injection Detection Layer
 *
 * Detects SQL injection attempts in request parameters, headers, and body.
 * Uses pattern matching and heuristic analysis to identify potential SQL injection attacks.
 */
final class SqlInjectionLayer implements LayerInterface
{
    private LayerConfig $config;

    private LayerMetrics $metrics;

    private bool $enabled = true;

    /** SQL Injection patterns (simplified for initial implementation) */
    private const SQL_PATTERNS = [
        // Classic injection patterns
        '/(\bunion\s+select\b)/i',
        '/(\bselect\b.*\bfrom\b)/i',
        '/(\binsert\s+into\b)/i',
        '/(\bupdate\s+.*\bset\b)/i',
        '/(\bdelete\s+from\b)/i',
        '/(\bdrop\s+(table|database)\b)/i',

        // SQL comment indicators
        '/(--|\#|\/\*|\*\/)/i',

        // SQL operators and functions
        '/(\bor\s+1\s*=\s*1\b)/i',
        '/(\band\s+1\s*=\s*1\b)/i',
        '/(\bor\s+\'[^\']*\'\s*=\s*\'[^\']*\'\b)/i',
        '/(\bunion\s+all\s+select\b)/i',

        // SQL string manipulation
        '/(\bconcat\s*\()/i',
        '/(\bchar\s*\()/i',
        '/(\bhex\s*\()/i',
        '/(\bascii\s*\()/i',

        // Database-specific functions
        '/(\bversion\s*\(\))/i',
        '/(\buser\s*\(\))/i',
        '/(\bdatabase\s*\(\))/i',
        '/(\bsleep\s*\()/i',
        '/(\bbenchmark\s*\()/i',

        // Blind injection patterns
        '/(\bwaitfor\s+delay\b)/i',
        '/(\bif\s*\(.*,.*,.*\))/i',

        // Quote and escape sequences
        '/([\'\"]\s*;\s*)/i',
        '/(\\\x[0-9a-f]{2})/i',
    ];

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

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

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

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

        // Debug logging
        file_put_contents('/tmp/waf_debug.log', "SqlInjectionLayer: Starting analysis for path: " . ($request->path ?? '/') . "\n", FILE_APPEND);
        file_put_contents('/tmp/waf_debug.log', "SqlInjectionLayer: Query params: " . json_encode($request->queryParams) . "\n", FILE_APPEND);

        try {
            // Check query parameters
            foreach ($request->queryParams as $key => $value) {
                if (is_string($value)) {
                    file_put_contents('/tmp/waf_debug.log', "SqlInjectionLayer: Analyzing query param '{$key}' = '{$value}'\n", FILE_APPEND);
                    $detection = $this->analyzeString($value, "query parameter '{$key}'");
                    if ($detection) {
                        file_put_contents('/tmp/waf_debug.log', "SqlInjectionLayer: DETECTION FOUND in query param '{$key}'!\n", FILE_APPEND);
                        $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 (common injection points)
            $suspiciousHeaders = ['User-Agent', 'Referer', 'X-Forwarded-For', 'Cookie'];
            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)) {
                file_put_contents('/tmp/waf_debug.log', "SqlInjectionLayer: Returning THREAT result with " . count($detections) . " detections\n", FILE_APPEND);

                return LayerResult::threat(
                    $this->getName(),
                    'SQL injection attempt detected',
                    LayerStatus::THREAT_DETECTED,
                    $detections,
                    $processingTime
                );
            }

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

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

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

    /**
     * Analyze string for SQL injection patterns
     */
    private function analyzeString(string $input, string $location): ?Detection
    {
        $originalInput = $input;
        $input = urldecode($input); // Decode URL encoding
        $input = html_entity_decode($input, ENT_QUOTES | ENT_HTML5); // Decode HTML entities

        file_put_contents('/tmp/waf_debug.log', "SqlInjectionLayer: analyzeString - original: '{$originalInput}', decoded: '{$input}'\n", FILE_APPEND);

        foreach (self::SQL_PATTERNS as $pattern) {
            file_put_contents('/tmp/waf_debug.log', "SqlInjectionLayer: Testing pattern: {$pattern}\n", FILE_APPEND);
            if (preg_match($pattern, $input, $matches)) {
                file_put_contents('/tmp/waf_debug.log', "SqlInjectionLayer: PATTERN MATCH! Pattern: {$pattern}, Match: " . ($matches[0] ?? '') . "\n", FILE_APPEND);

                try {
                    $detection = new Detection(
                        category: DetectionCategory::SQL_INJECTION,
                        severity: DetectionSeverity::CRITICAL,
                        message: "SQL injection pattern detected in {$location}: " . ($matches[0] ?? ''),
                        ruleId: null,
                        confidence: Percentage::from(90.0),
                        payload: null,
                        location: $location,
                        timestamp: null, // Will be set automatically by static methods if needed
                        context: null
                    );
                    file_put_contents('/tmp/waf_debug.log', "SqlInjectionLayer: Created Detection object, returning it\n", FILE_APPEND);

                    return $detection;
                } catch (\Throwable $e) {
                    file_put_contents('/tmp/waf_debug.log', "SqlInjectionLayer: ERROR creating Detection: " . $e->getMessage() . "\n", FILE_APPEND);
                    // Continue to next pattern
                }
            }
        }

        file_put_contents('/tmp/waf_debug.log', "SqlInjectionLayer: No patterns matched for input: '{$input}'\n", FILE_APPEND);

        return null;
    }

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

    public function isHealthy(): bool
    {
        return true; // Simple implementation - always healthy
    }

    public function getPriority(): int
    {
        return 100; // High priority for SQL injection detection
    }

    public function getConfidenceLevel(): Percentage
    {
        return $this->config->getEffectiveConfidenceThreshold();
    }

    public function getTimeoutThreshold(): Duration
    {
        return $this->config->getEffectiveTimeout();
    }

    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 []; // No dependencies
    }

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

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

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