<?php

declare(strict_types=1);

namespace App\Framework\Waf;

use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Waf\ValueObjects\Detection;
use App\Framework\Waf\ValueObjects\DetectionCollection;

/**
 * Unified threat assessment system
 * Analyzes and scores all detections from WAF layers
 */
final readonly class ThreatAssessment
{
    public function __construct(
        public DetectionCollection $detections,
        public Percentage $overallThreatScore,
        public DetectionSeverity $maxSeverity,
        public int $owaspTop10Count,
        public int $criticalCount,
        public int $highCount,
        public Percentage $averageConfidence,
        public ?Timestamp $assessmentTime = null
    ) {
    }

    /**
     * Create assessment from detection collection
     */
    public static function fromDetections(DetectionCollection $detections): self
    {
        if ($detections->isEmpty()) {
            return self::empty();
        }

        $overallScore = self::calculateOverallThreatScore($detections);
        $maxSeverity = self::findMaxSeverity($detections);
        $owaspTop10Count = self::countOwaspTop10($detections);
        $criticalCount = self::countBySeverity($detections, DetectionSeverity::CRITICAL);
        $highCount = self::countBySeverity($detections, DetectionSeverity::HIGH);
        $averageConfidence = self::calculateAverageConfidence($detections);

        return new self(
            detections: $detections,
            overallThreatScore: $overallScore,
            maxSeverity: $maxSeverity,
            owaspTop10Count: $owaspTop10Count,
            criticalCount: $criticalCount,
            highCount: $highCount,
            averageConfidence: $averageConfidence,
            assessmentTime: Timestamp::fromFloat(microtime(true))
        );
    }

    /**
     * Create empty assessment
     */
    public static function empty(): self
    {
        return self::createEmpty();
    }

    /**
     * Create empty assessment
     */
    public static function createEmpty(): self
    {
        return new self(
            detections: DetectionCollection::empty(),
            overallThreatScore: Percentage::from(0.0),
            maxSeverity: DetectionSeverity::INFO,
            owaspTop10Count: 0,
            criticalCount: 0,
            highCount: 0,
            averageConfidence: Percentage::from(0.0),
            assessmentTime: Timestamp::fromFloat(microtime(true))
        );
    }

    /**
     * Check if request should be blocked
     */
    public function shouldBlock(): bool
    {
        // Block if any detection explicitly requires blocking
        if ($this->hasBlockingDetection()) {
            return true;
        }

        // Block based on threat score threshold
        if ($this->overallThreatScore->getValue() >= 80.0) {
            return true;
        }

        // Block if multiple high-severity threats
        if ($this->criticalCount > 0 || $this->highCount >= 3) {
            return true;
        }

        // Block if multiple OWASP Top 10 violations
        if ($this->owaspTop10Count >= 2) {
            return true;
        }

        return false;
    }

    /**
     * Check if assessment should trigger alert
     */
    public function shouldAlert(): bool
    {
        return $this->maxSeverity->shouldAlert() ||
               $this->overallThreatScore->getValue() >= 50.0 ||
               $this->owaspTop10Count > 0;
    }

    /**
     * Check if assessment requires immediate action
     */
    public function requiresImmediateAction(): bool
    {
        return $this->criticalCount > 0 ||
               $this->maxSeverity->requiresImmediateAction() ||
               $this->overallThreatScore->getValue() >= 90.0;
    }

    /**
     * Get risk level description
     */
    public function getRiskLevel(): string
    {
        $score = $this->overallThreatScore->getValue();

        return match (true) {
            $score >= 90.0 => 'CRITICAL',
            $score >= 70.0 => 'HIGH',
            $score >= 40.0 => 'MEDIUM',
            $score >= 10.0 => 'LOW',
            default => 'MINIMAL'
        };
    }

    /**
     * Get threat categories present in assessment
     */
    public function getThreatCategories(): array
    {
        $categories = [];

        foreach ($this->detections->detections as $detection) {
            $categories[$detection->category->value] = $detection->category;
        }

        return array_values($categories);
    }

    /**
     * Get detections by severity
     */
    public function getDetectionsBySeverity(DetectionSeverity $severity): DetectionCollection
    {
        return $this->detections->filterBySeverity($severity);
    }

    /**
     * Get detections by category
     */
    public function getDetectionsByCategory(DetectionCategory $category): DetectionCollection
    {
        return $this->detections->filterByCategory($category);
    }

    /**
     * Get OWASP Top 10 detections
     */
    public function getOwaspTop10Detections(): DetectionCollection
    {
        return $this->detections->filter(fn (Detection $detection) => $detection->isOwaspTop10());
    }

    /**
     * Get blocking detections
     */
    public function getBlockingDetections(): DetectionCollection
    {
        return $this->detections->filter(fn (Detection $detection) => $detection->shouldBlock());
    }

    /**
     * Check if assessment has any blocking detection
     */
    public function hasBlockingDetection(): bool
    {
        return ! $this->getBlockingDetections()->isEmpty();
    }

    /**
     * Check if assessment is empty
     */
    public function isEmpty(): bool
    {
        return $this->detections->isEmpty();
    }

    /**
     * Get total detection count
     */
    public function getDetectionCount(): int
    {
        return $this->detections->count();
    }

    /**
     * Merge with another assessment
     */
    public function merge(self $other): self
    {
        if ($other->isEmpty()) {
            return $this;
        }

        if ($this->isEmpty()) {
            return $other;
        }

        $mergedDetections = $this->detections->merge($other->detections);

        return self::fromDetections($mergedDetections);
    }

    /**
     * Convert to array for serialization
     */
    public function toArray(): array
    {
        return [
            'overall_threat_score' => $this->overallThreatScore->getValue(),
            'risk_level' => $this->getRiskLevel(),
            'max_severity' => $this->maxSeverity->value,
            'detection_count' => $this->getDetectionCount(),
            'owasp_top10_count' => $this->owaspTop10Count,
            'critical_count' => $this->criticalCount,
            'high_count' => $this->highCount,
            'average_confidence' => $this->averageConfidence->getValue(),
            'should_block' => $this->shouldBlock(),
            'should_alert' => $this->shouldAlert(),
            'requires_immediate_action' => $this->requiresImmediateAction(),
            'threat_categories' => array_map(fn ($cat) => $cat->value, $this->getThreatCategories()),
            'assessment_time' => $this->assessmentTime?->toIsoString(),
            'detections' => $this->detections->toArray(),
        ];
    }

    /**
     * Calculate overall threat score from detections
     */
    private static function calculateOverallThreatScore(DetectionCollection $detections): Percentage
    {
        if ($detections->isEmpty()) {
            return Percentage::from(0.0);
        }

        $totalScore = 0.0;
        $maxScore = 0.0;
        $count = 0;

        foreach ($detections->detections as $detection) {
            $threatScore = $detection->getThreatScore()->getValue();
            $totalScore += $threatScore;
            $maxScore = max($maxScore, $threatScore);
            $count++;
        }

        // Use weighted average: 70% average + 30% maximum
        $averageScore = $totalScore / $count;
        $finalScore = ($averageScore * 0.7) + ($maxScore * 0.3);

        // Apply severity multipliers
        $criticalCount = self::countBySeverity($detections, DetectionSeverity::CRITICAL);
        $highCount = self::countBySeverity($detections, DetectionSeverity::HIGH);

        if ($criticalCount > 0) {
            $finalScore = min(100.0, $finalScore * (1.0 + ($criticalCount * 0.2)));
        }

        if ($highCount > 0) {
            $finalScore = min(100.0, $finalScore * (1.0 + ($highCount * 0.1)));
        }

        return Percentage::from($finalScore);
    }

    /**
     * Find maximum severity in detections
     */
    private static function findMaxSeverity(DetectionCollection $detections): DetectionSeverity
    {
        $maxSeverity = DetectionSeverity::INFO;

        foreach ($detections->detections as $detection) {
            if ($detection->severity->getCvssScore() > $maxSeverity->getCvssScore()) {
                $maxSeverity = $detection->severity;
            }
        }

        return $maxSeverity;
    }

    /**
     * Count OWASP Top 10 detections
     */
    private static function countOwaspTop10(DetectionCollection $detections): int
    {
        $count = 0;

        foreach ($detections->detections as $detection) {
            if ($detection->isOwaspTop10()) {
                $count++;
            }
        }

        return $count;
    }

    /**
     * Count detections by severity
     */
    private static function countBySeverity(DetectionCollection $detections, DetectionSeverity $severity): int
    {
        $count = 0;

        foreach ($detections->detections as $detection) {
            if ($detection->severity === $severity) {
                $count++;
            }
        }

        return $count;
    }

    /**
     * Calculate average confidence from detections
     */
    private static function calculateAverageConfidence(DetectionCollection $detections): Percentage
    {
        if ($detections->isEmpty()) {
            return Percentage::from(0.0);
        }

        $totalConfidence = 0.0;
        $count = 0;

        foreach ($detections->detections as $detection) {
            if ($detection->confidence !== null) {
                $totalConfidence += $detection->confidence->getValue();
                $count++;
            }
        }

        if ($count === 0) {
            return Percentage::from(0.0);
        }

        return Percentage::from($totalConfidence / $count);
    }
}
