<?php

declare(strict_types=1);

namespace App\Framework\Waf\Feedback;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\DateTime\Clock;
use App\Framework\Http\Request;
use App\Framework\Logging\Logger;
use App\Framework\Waf\DetectionSeverity;
use App\Framework\Waf\LayerResult;
use App\Framework\Waf\LayerStatus;
use App\Framework\Waf\ThreatAssessment;
use App\Framework\Waf\ValueObjects\Detection;
use App\Framework\Waf\ValueObjects\DetectionCollection;
use App\Framework\Waf\WafDecision;
use App\Framework\Waf\WafEngine;

/**
 * Integrates WAF feedback system with the WAF engine using composition
 */
final readonly class WafFeedbackIntegrator
{
    /**
     * @param WafEngine $wafEngine The WAF engine to integrate with
     * @param FeedbackService $feedbackService The feedback service to use
     * @param Clock $clock Clock for getting current time
     * @param Logger $logger Logger for logging events
     * @param float $falsePositiveThreshold Threshold for considering feedback when making decisions (0.0-1.0)
     */
    public function __construct(
        private WafEngine $wafEngine,
        private FeedbackService $feedbackService,
        private Clock $clock,
        private Logger $logger,
        private float $falsePositiveThreshold = 0.7
    ) {
    }

    /**
     * Analyze request through WAF engine, considering feedback data
     *
     * @param Request $request The request to analyze
     * @return LayerResult The analysis result
     */
    public function analyze(Request $request): LayerResult
    {
        // First, get the standard WAF analysis
        $result = $this->wafEngine->analyze($request);

        // If there are no detections, return the result as is
        if (! $result->hasDetections()) {
            return $result;
        }

        // Get the detections from the result
        $detections = $result->getDetections();

        // Process each detection and check for false positive feedback
        $processedDetections = [];
        $removedDetections = [];

        foreach ($detections->detections as $detection) {
            // Check if this detection has false positive feedback
            $feedback = $this->feedbackService->getFeedbackForDetection($detection->id);

            // Count false positive feedback
            $falsePositiveCount = 0;
            $totalFeedbackCount = count($feedback);

            foreach ($feedback as $item) {
                if ($item->feedbackType === FeedbackType::FALSE_POSITIVE) {
                    $falsePositiveCount++;
                }
            }

            // Calculate false positive ratio
            $falsePositiveRatio = $totalFeedbackCount > 0
                ? $falsePositiveCount / $totalFeedbackCount
                : 0.0;

            // If false positive ratio is above threshold, remove the detection
            if ($totalFeedbackCount >= 3 && $falsePositiveRatio >= $this->falsePositiveThreshold) {
                $this->logger->info('Removing detection based on feedback', [
                    'detection_id' => $detection->id,
                    'false_positive_ratio' => $falsePositiveRatio,
                    'false_positive_count' => $falsePositiveCount,
                    'total_feedback_count' => $totalFeedbackCount,
                ]);

                $removedDetections[] = $detection;

                continue;
            }

            // Check for severity adjustment feedback
            $suggestedSeverity = $this->getSuggestedSeverity($feedback);
            if ($suggestedSeverity !== null && $suggestedSeverity !== $detection->severity) {
                // Create a new detection with adjusted severity
                $adjustedDetection = new Detection(
                    $detection->category,
                    $suggestedSeverity,
                    $detection->message,
                    null, // ruleId
                    null, // confidence
                    null, // payload
                    null, // location
                    $detection->timestamp
                );

                $this->logger->info('Adjusted detection severity based on feedback', [
                    'detection_id' => $detection->id,
                    'original_severity' => $detection->severity->value,
                    'adjusted_severity' => $suggestedSeverity->value,
                ]);

                $processedDetections[] = $adjustedDetection;
            } else {
                // Keep the original detection
                $processedDetections[] = $detection;
            }
        }

        // If we removed or adjusted any detections, create a new result
        if (! empty($removedDetections) || count($processedDetections) !== count($detections->detections)) {
            // If all detections were removed, return a clean result
            if (empty($processedDetections)) {
                return LayerResult::clean(
                    'waf_feedback_integrator',
                    'All detections were removed based on feedback'
                );
            }

            // Create a new detection collection with the processed detections
            /** @var array<\App\Framework\Waf\Detection> $processedDetections */
            $newDetections = new DetectionCollection($processedDetections);

            // Determine the new status based on the remaining detections
            $highestSeverity = DetectionSeverity::INFO;
            foreach ($processedDetections as $detection) {
                if ($detection->severity->value > $highestSeverity->value) {
                    $highestSeverity = $detection->severity;
                }
            }

            $newStatus = match (true) {
                $highestSeverity === DetectionSeverity::CRITICAL || $highestSeverity === DetectionSeverity::HIGH
                    => LayerStatus::THREAT_DETECTED,
                $highestSeverity === DetectionSeverity::MEDIUM || $highestSeverity === DetectionSeverity::LOW
                    => LayerStatus::NEUTRAL,
                default => LayerStatus::CLEAN
            };

            // Calculate threat score based on detections
            $maxRiskScore = 0.0;
            foreach ($processedDetections as $detection) {
                $threatScore = $detection->getThreatScore()->getValue();
                if ($threatScore > $maxRiskScore) {
                    $maxRiskScore = $threatScore;
                }
            }

            if ($newStatus === LayerStatus::THREAT_DETECTED) {
                return LayerResult::threat(
                    'waf_feedback_integrator',
                    'Detections adjusted based on feedback',
                    LayerStatus::THREAT_DETECTED,
                    $processedDetections,
                    $result->executionDuration
                );
            } elseif ($newStatus === LayerStatus::NEUTRAL) {
                return LayerResult::neutral(
                    'waf_feedback_integrator',
                    'Detections adjusted based on feedback'
                )->withExecutionDuration($result->executionDuration ?? Duration::zero());
            } else {
                return LayerResult::clean(
                    'waf_feedback_integrator',
                    'Detections adjusted based on feedback',
                    $result->executionDuration
                );
            }
        }

        // If no changes were made, return the original result
        return $result;
    }

    /**
     * Analyze request and return a WAF decision, considering feedback data
     *
     * @param Request $request The request to analyze
     * @return WafDecision The WAF decision
     */
    public function analyzeRequest(Request $request): WafDecision
    {
        // First, get the standard WAF decision
        $decision = $this->wafEngine->analyzeRequest($request);

        // If the decision is to allow, return it as is
        if ($decision->action === \App\Framework\Waf\WafAction::ALLOW) {
            return $decision;
        }

        // Get the threat assessment from the decision
        $assessment = $decision->assessment;

        // Check for false positive feedback for each detection
        $detections = $assessment->detections;
        $removedDetectionCount = 0;

        foreach ($detections->detections as $detection) {
            // Check if this detection has false positive feedback
            $feedback = $this->feedbackService->getFeedbackForDetection($detection->id);

            // Count false positive feedback
            $falsePositiveCount = 0;
            $totalFeedbackCount = count($feedback);

            foreach ($feedback as $item) {
                if ($item->feedbackType === FeedbackType::FALSE_POSITIVE) {
                    $falsePositiveCount++;
                }
            }

            // Calculate false positive ratio
            $falsePositiveRatio = $totalFeedbackCount > 0
                ? $falsePositiveCount / $totalFeedbackCount
                : 0.0;

            // If false positive ratio is above threshold, consider this a false positive
            if ($totalFeedbackCount >= 3 && $falsePositiveRatio >= $this->falsePositiveThreshold) {
                $removedDetectionCount++;
            }
        }

        // If all detections are considered false positives, allow the request
        if ($removedDetectionCount === count($detections->detections)) {
            $this->logger->info('Allowing request based on feedback', [
                'removed_detection_count' => $removedDetectionCount,
                'total_detection_count' => count($detections->detections),
            ]);

            return WafDecision::allow(
                ThreatAssessment::createEmpty(),
                Duration::zero()
            );
        }

        // If some detections are considered false positives, but not all,
        // adjust the threat score proportionally
        if ($removedDetectionCount > 0) {
            $adjustmentFactor = 1.0 - ($removedDetectionCount / count($detections->detections));
            $adjustedThreatScore = $assessment->threatScore->getValue() * $adjustmentFactor;

            $this->logger->info('Adjusted threat score based on feedback', [
                'original_threat_score' => $assessment->threatScore->getValue(),
                'adjusted_threat_score' => $adjustedThreatScore,
                'adjustment_factor' => $adjustmentFactor,
                'removed_detection_count' => $removedDetectionCount,
                'total_detection_count' => count($detections->detections),
            ]);

            // If the adjusted threat score is below the block threshold, allow the request
            if ($adjustedThreatScore < 0.7) { // Assuming 0.7 is the block threshold
                return WafDecision::allow(
                    $assessment,
                    $decision->processingTime ?? Duration::zero()
                );
            }
        }

        // Otherwise, return the original decision
        return $decision;
    }

    /**
     * Submit feedback for a detection
     *
     * @param Detection $detection The detection to submit feedback for
     * @param FeedbackType $feedbackType The type of feedback
     * @param string $userId The ID of the user submitting the feedback
     * @param string|null $comment Optional comment
     * @param array<string, mixed> $context Additional context
     * @return DetectionFeedback The submitted feedback
     */
    public function submitFeedback(
        Detection $detection,
        FeedbackType $feedbackType,
        string $userId,
        ?string $comment = null,
        array $context = []
    ): DetectionFeedback {
        return $this->feedbackService->submitFeedbackForDetection(
            $detection,
            $feedbackType,
            $userId,
            $comment,
            $context
        );
    }

    /**
     * Get the suggested severity from feedback
     *
     * @param DetectionFeedback[] $feedback The feedback to analyze
     * @return DetectionSeverity|null The suggested severity, or null if no consensus
     */
    private function getSuggestedSeverity(array $feedback): ?DetectionSeverity
    {
        // Count suggested severities
        $severityCounts = [];
        $totalSeverityAdjustments = 0;

        foreach ($feedback as $item) {
            if ($item->feedbackType === FeedbackType::SEVERITY_ADJUSTMENT) {
                $suggestedSeverity = $item->getSuggestedSeverity();
                if ($suggestedSeverity !== null) {
                    $severityCounts[$suggestedSeverity->value] = ($severityCounts[$suggestedSeverity->value] ?? 0) + 1;
                    $totalSeverityAdjustments++;
                }
            }
        }

        // If there are no severity adjustments, return null
        if ($totalSeverityAdjustments === 0) {
            return null;
        }

        // Find the most suggested severity
        $mostSuggestedSeverity = null;
        $mostSuggestedCount = 0;

        foreach ($severityCounts as $severity => $count) {
            if ($count > $mostSuggestedCount) {
                $mostSuggestedSeverity = $severity;
                $mostSuggestedCount = $count;
            }
        }

        // If there's a clear consensus (more than 50% of suggestions), return it
        if ($mostSuggestedSeverity !== null && $mostSuggestedCount > ($totalSeverityAdjustments / 2)) {
            return DetectionSeverity::from($mostSuggestedSeverity);
        }

        // Otherwise, return null (no consensus)
        return null;
    }

    /**
     * Get the WAF engine
     */
    public function getWafEngine(): WafEngine
    {
        return $this->wafEngine;
    }

    /**
     * Get the feedback service
     */
    public function getFeedbackService(): FeedbackService
    {
        return $this->feedbackService;
    }

    /**
     * Get the false positive threshold
     */
    public function getFalsePositiveThreshold(): float
    {
        return $this->falsePositiveThreshold;
    }

    /**
     * Set the false positive threshold
     *
     * @param float $threshold The new threshold (0.0-1.0)
     * @return self
     */
    public function withFalsePositiveThreshold(float $threshold): self
    {
        return new self(
            $this->wafEngine,
            $this->feedbackService,
            $this->clock,
            $this->logger,
            max(0.0, min(1.0, $threshold))
        );
    }
}
