<?php

declare(strict_types=1);

namespace App\Framework\Waf;

use App\Framework\Config\WafConfig;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\Clock;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Request;
use App\Framework\Logging\Logger;
use App\Framework\Performance\PerformanceService;
use App\Framework\Waf\Analysis\ValueObjects\RequestAnalysisData;
use App\Framework\Waf\Layers\LayerInterface;
use App\Framework\Waf\MachineLearning\MachineLearningEngine;

/**
 * Central WAF Engine - Orchestrates all security layers
 * Provides unified threat assessment and decision making
 */
final class WafEngine
{
    /** @var LayerInterface[] */
    private array $layers = [];

    /** @var array<string, mixed> */
    private array $layerResults = [];

    public function __construct(
        private readonly WafConfig $config,
        private readonly ThreatAssessmentService $threatAssessmentService,
        private readonly PerformanceService $performance,
        private readonly Logger $logger,
        private readonly Clock $clock,
        private readonly ?MachineLearningEngine $mlEngine = null
    ) {
    }

    /**
     * Register a security layer
     */
    public function registerLayer(LayerInterface $layer): void
    {
        $this->layers[$layer->getName()] = $layer;
    }

    /**
     * Analyze request through all registered layers (alias for analyzeRequest)
     */
    public function analyze(Request $request): LayerResult
    {
        if (! $this->config->enabled) {
            return LayerResult::clean('waf_engine', 'WAF disabled');
        }

        if (empty($this->layers)) {
            return LayerResult::clean('waf_engine', 'No security layers registered');
        }

        // Process all layers and aggregate results
        $allDetections = [];
        $highestRisk = 0.0;
        $threatDetected = false;

        foreach ($this->layers as $layerName => $layer) {
            if (! $layer->isEnabled()) {
                $this->logger->debug("WafEngine: Layer '{$layerName}' is disabled, skipping");

                continue;
            }

            $this->logger->debug("WafEngine: Analyzing with layer '{$layerName}'");

            try {
                $layerResult = $layer->analyze($request);

                $this->logger->debug("WafEngine: Layer '{$layerName}' returned status: " . $layerResult->status->value . ", has detections: " . ($layerResult->hasDetections() ? 'YES' : 'NO'));

                if ($layerResult->hasDetections()) {
                    $detectionCollection = $layerResult->getDetections();
                    $detections = $detectionCollection->detections; // Access the array property
                    $allDetections = array_merge($allDetections, $detections);

                    // Check if any detection indicates a threat
                    foreach ($detections as $detection) {
                        $threatScore = $detection->getThreatScore()->getValue();
                        if ($threatScore > $highestRisk) {
                            $highestRisk = $threatScore;
                        }

                        if ($detection->severity === \App\Framework\Waf\DetectionSeverity::CRITICAL ||
                            $detection->severity === \App\Framework\Waf\DetectionSeverity::HIGH) {
                            $threatDetected = true;
                        }
                    }
                }
            } catch (\Throwable $e) {
                $this->logger->error('WAF layer analysis failed', [
                    'layer' => $layerName,
                    'error' => $e->getMessage(),
                ]);
                // Continue with other layers
            }
        }

        // Determine overall result
        $this->logger->debug("WafEngine: Final decision - threatDetected: " . ($threatDetected ? 'YES' : 'NO') . ", highestRisk: {$highestRisk}, detections: " . count($allDetections));

        if ($threatDetected && $highestRisk >= 70.0) {
            $this->logger->debug("WafEngine: Returning THREAT result");

            return LayerResult::threat(
                'waf_engine',
                'Security threat detected',
                LayerStatus::THREAT_DETECTED,
                $allDetections
            );
        } elseif (! empty($allDetections)) {
            $this->logger->debug("WafEngine: Returning SUSPICIOUS result");

            return LayerResult::suspicious(
                'waf_engine',
                'Suspicious activity detected',
                $allDetections
            );
        }

        $this->logger->debug("WafEngine: Returning CLEAN result");

        return LayerResult::clean('waf_engine', 'All security layers passed');
    }

    /**
     * Analyze request through all registered layers
     */
    public function analyzeRequest(Request $request): WafDecision
    {
        if (! $this->config->enabled) {
            // Create empty assessment and return allow decision
            $emptyAssessment = ThreatAssessment::createEmpty();

            return WafDecision::allow($emptyAssessment, Duration::zero());
        }

        return $this->performance->measure('waf_analysis', function () use ($request) {
            $analysisStart = $this->clock->now();
            $this->layerResults = [];

            // Process all layers in parallel for performance
            $layerPromises = [];

            foreach ($this->layers as $layerName => $layer) {
                if (! $layer->isEnabled()) {
                    continue;
                }

                $layerPromises[$layerName] = $this->processLayer($layer, $request);
            }

            // Collect all layer results
            foreach ($layerPromises as $layerName => $promise) {
                $this->layerResults[$layerName] = $promise;
            }

            $analysisEnd = $this->clock->now();
            $endTime = $analysisEnd->getTimestamp() + (float)$analysisEnd->format('u') / 1_000_000;
            $startTime = $analysisStart->getTimestamp() + (float)$analysisStart->format('u') / 1_000_000;
            $totalDuration = Duration::fromSeconds($endTime - $startTime);

            // Perform machine learning analysis if enabled
            $mlResult = null;
            if ($this->mlEngine?->isEnabled()) {
                $requestData = $this->createRequestAnalysisData($request);
                $mlResult = $this->mlEngine->analyzeRequest($requestData, ['layer_results' => $this->layerResults]);
            }

            // Create unified threat assessment
            return $this->threatAssessmentService->evaluate($this->layerResults, $request, $totalDuration, $mlResult);
        }, \App\Framework\Performance\PerformanceCategory::SECURITY);
    }

    /**
     * Process individual security layer
     */
    private function processLayer(LayerInterface $layer, Request $request): LayerResult
    {
        $startTime = $this->clock->now();

        try {
            $result = $layer->analyze($request);

            $endTime = $this->clock->now();
            $endTimestamp = $endTime->getTimestamp() + (float)$endTime->format('u') / 1_000_000;
            $startTimestamp = $startTime->getTimestamp() + (float)$startTime->format('u') / 1_000_000;
            $executionDuration = Duration::fromSeconds($endTimestamp - $startTimestamp);

            // Log slow layers for performance optimization
            if ($executionDuration->greaterThan($this->config->globalTimeout)) {
                $this->logger->warning('Slow WAF layer detected', [
                    'layer' => $layer->getName(),
                            'execution_duration' => $executionDuration->toMilliseconds(),
                    'threat_score' => 0.0, // Simplified for now
                ]);
            }

            return $result->withExecutionDuration($executionDuration);

        } catch (\Throwable $e) {
            $this->logger->error('WAF layer failed', [
                'layer' => $layer->getName(),
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            // Return neutral result on layer failure - fail open for availability
            return LayerResult::neutral($layer->getName(), 'Layer execution failed: ' . $e->getMessage());
        }
    }

    /**
     * Get detailed analysis results for debugging/monitoring
     * @return array<string, mixed>
     */
    public function getLayerResults(): array
    {
        return $this->layerResults;
    }

    /**
     * Get performance statistics for all layers
     * @return array<string, mixed>
     */
    public function getPerformanceStats(): array
    {
        $stats = [];

        foreach ($this->layerResults as $layerName => $result) {
            $stats[$layerName] = [
                'execution_duration' => Duration::zero(),
                'threat_score' => Percentage::zero(),
                'detections' => 0,
                'status' => 'unknown',
            ];
        }

        return $stats;
    }

    /**
     * Update WAF configuration at runtime
     */
    public function updateConfig(WafConfig $config): void
    {
        // Note: Cannot update readonly property, would need to recreate the engine
        // For now, we'll log the configuration change
        // $this->config = $config;

        $this->logger->info('WAF configuration updated', [
            'enabled' => $config->enabled,
            'layers_count' => count($this->layers),
            'timestamp' => $this->clock->now(),
        ]);
    }

    /**
     * Get current WAF health status
     * @return array<string, mixed>
     */
    public function getHealthStatus(): array
    {
        $healthyLayers = 0;
        $totalLayers = count($this->layers);

        foreach ($this->layers as $layer) {
            if ($layer->isHealthy()) {
                $healthyLayers++;
            }
        }

        $healthPercentage = $totalLayers > 0
            ? Percentage::fromRatio($healthyLayers, $totalLayers)
            : Percentage::from(100.0);

        return [
            'status' => $healthyLayers === $totalLayers ? 'healthy' : 'degraded',
            'health_percentage' => $healthPercentage,
            'healthy_layers' => $healthyLayers,
            'total_layers' => $totalLayers,
            'enabled' => $this->config->enabled,
            'average_processing_time' => $this->getAverageProcessingTime(),
            'last_check' => Timestamp::fromClock($this->clock),
        ];
    }

    private function getAverageProcessingTime(): Duration
    {
        if (empty($this->layerResults)) {
            return Duration::zero();
        }

        $totalMilliseconds = 0.0;
        foreach ($this->layerResults as $result) {
            // Simplified for now
            $totalMilliseconds += 0.0;
        }

        $averageMs = $totalMilliseconds / count($this->layerResults);

        return Duration::fromMilliseconds($averageMs);
    }

    /**
     * Create RequestAnalysisData from HTTP Request for ML analysis
     */
    private function createRequestAnalysisData(Request $request): RequestAnalysisData
    {
        if (! $request instanceof HttpRequest) {
            throw new \RuntimeException('Expected HttpRequest instance');
        }

        $httpRequest = $request;

        // Simplified implementation - just create with basic data
        return new RequestAnalysisData(
            method: $httpRequest->method->value,
            url: $httpRequest->getUri()->__toString(),
            path: $httpRequest->path,
            queryString: http_build_query($httpRequest->queryParams),
            headers: [], // Simplified for now
            queryParameters: $httpRequest->queryParams,
            postParameters: $httpRequest->parsedBody->data ?? [],
            cookies: [],
            body: $httpRequest->body, // Simplified for now
            files: [], // Simplified for now
            clientIp: $httpRequest->server->getRemoteAddr(),  // Will be string or empty
            userAgent: null, // Simplified for now
            contentType: null, // Simplified for now
            contentLength: null, // Simplified for now
            timestamp: Timestamp::fromClock($this->clock)
        );
    }
}
