<?php

declare(strict_types=1);

namespace App\Framework\Waf\MachineLearning\Detectors;

use App\Framework\MachineLearning\Core\AnomalyDetectorInterface;
use App\Framework\MachineLearning\ValueObjects\AnomalyDetection;
use App\Framework\MachineLearning\ValueObjects\AnomalyType;
use App\Framework\MachineLearning\ValueObjects\Baseline;
use App\Framework\MachineLearning\ValueObjects\Feature;
use App\Framework\MachineLearning\ValueObjects\FeatureType;

/**
 * Statistical anomaly detector using Z-score and other statistical methods
 */
final class StatisticalAnomalyDetector implements AnomalyDetectorInterface
{
    public function __construct(
        private readonly bool $enabled = true,
        private float $confidenceThreshold = 0.75,
        private readonly float $zScoreThreshold = 2.0,
        private readonly float $extremeZScoreThreshold = 3.0,
        private readonly int $minSampleSize = 20,
        private readonly bool $enableOutlierDetection = true,
        private readonly bool $enableTrendAnalysis = true,
        private array $featureHistory = [] // For trend analysis
    ) {
    }

    public function getName(): string
    {
        return 'Statistical Anomaly Detector';
    }

    public function getSupportedFeatureTypes(): array
    {
        return [
            FeatureType::FREQUENCY,
            FeatureType::STRUCTURAL_PATTERN,
            FeatureType::TIME_DISTRIBUTION,
            FeatureType::FAILURE_PATTERN,
            FeatureType::LATENCY,
        ];
    }

    public function canAnalyze(array $features): bool
    {
        if (empty($features)) {
            return false;
        }

        // Check if we have features from supported behavior types
        foreach ($features as $feature) {
            if ($feature instanceof Feature &&
                in_array($feature->type, $this->getSupportedFeatureTypes(), true)) {
                return true;
            }
        }

        return false;
    }

    public function detectAnomalies(array $features, ?Baseline $baseline = null): array
    {
        if (!$this->enabled) {
            return [];
        }

        $anomalies = [];

        foreach ($features as $feature) {
            if (! ($feature instanceof Feature)) {
                continue;
            }

            // Z-score based anomaly detection
            $zScoreAnomalies = $this->detectZScoreAnomalies($feature, $baseline);
            $anomalies = array_merge($anomalies, $zScoreAnomalies);

            // Outlier detection using IQR method
            if ($this->enableOutlierDetection) {
                $outlierAnomalies = $this->detectOutliers($feature);
                $anomalies = array_merge($anomalies, $outlierAnomalies);
            }

            // Trend-based anomaly detection
            if ($this->enableTrendAnalysis) {
                $trendAnomalies = $this->detectTrendAnomalies($feature);
                $anomalies = array_merge($anomalies, $trendAnomalies);
            }

            // Frequency spike detection
            if ($feature->type === FeatureType::FREQUENCY) {
                $spikeAnomalies = $this->detectFrequencySpikes($feature, $baseline);
                $anomalies = array_merge($anomalies, $spikeAnomalies);
            }

            // Pattern deviation detection
            if ($feature->type === FeatureType::STRUCTURAL_PATTERN) {
                $patternAnomalies = $this->detectPatternDeviations($feature, $baseline);
                $anomalies = array_merge($anomalies, $patternAnomalies);
            }

            // Store feature for historical analysis (after detection to not pollute baseline)
            $this->recordFeature($feature);
        }

        // Filter anomalies by confidence threshold
        return array_filter(
            $anomalies,
            fn (AnomalyDetection $anomaly) => $anomaly->confidence->getValue() >= $this->confidenceThreshold * 100
        );
    }

    /**
     * Detect Z-score based statistical anomalies
     */
    private function detectZScoreAnomalies(Feature $feature, ?Baseline $baseline): array
    {
        $anomalies = [];

        // Use baseline if provided, otherwise use feature's own Z-score
        if ($baseline !== null && $baseline->type === $feature->type) {
            $zScore = $baseline->calculateZScore($feature->value);
            $anomalyScore = $baseline->getAnomalyScore($feature->value);
        } elseif ($feature->zScore !== null) {
            $zScore = $feature->zScore;
            $anomalyScore = $feature->getAnomalyScore();
        } else {
            return $anomalies;
        }

        $absZScore = abs($zScore);

        // Extreme anomaly (> 3 sigma)
        if ($absZScore > $this->extremeZScoreThreshold) {
            $anomalies[] = AnomalyDetection::statisticalAnomaly(
                featureType: $feature->type,
                metric: $feature->name,
                value: $feature->value,
                expectedValue: $baseline?->mean ?? 0.0,
                standardDeviation: $baseline?->standardDeviation ?? 1.0
            );
        }
        // Moderate anomaly (> 2 sigma)
        elseif ($absZScore > $this->zScoreThreshold) {
            $confidence = min(90.0, 60.0 + ($absZScore - $this->zScoreThreshold) * 30.0);

            $anomaly = AnomalyDetection::create(
                type: AnomalyType::STATISTICAL_ANOMALY,
                featureType: $feature->type,
                anomalyScore: $anomalyScore,
                description: "Moderate statistical anomaly in {$feature->name}: Z-score = " . round($zScore, 2),
                features: [$feature],
                evidence: [
                    'z_score' => $zScore,
                    'threshold' => $this->zScoreThreshold,
                    'feature_value' => $feature->value,
                    'baseline_mean' => $baseline?->mean,
                    'baseline_std_dev' => $baseline?->standardDeviation,
                ]
            );

            $anomalies[] = $anomaly;
        }

        return $anomalies;
    }

    /**
     * Detect outliers using Interquartile Range (IQR) method
     */
    private function detectOutliers(Feature $feature): array
    {
        $history = $this->getFeatureHistory($feature->type, $feature->name);

        if (count($history) < $this->minSampleSize) {
            return [];
        }

        sort($history);

        $q1Index = (int)(count($history) * 0.25);
        $q3Index = (int)(count($history) * 0.75);

        $q1 = $history[$q1Index];
        $q3 = $history[$q3Index];
        $iqr = $q3 - $q1;

        $lowerBound = $q1 - 1.5 * $iqr;
        $upperBound = $q3 + 1.5 * $iqr;

        // Check if current value is an outlier
        if ($feature->value < $lowerBound || $feature->value > $upperBound) {
            $distance = min(abs($feature->value - $lowerBound), abs($feature->value - $upperBound));
            $anomalyScore = min(1.0, $distance / max($iqr, 0.01));

            return [
                AnomalyDetection::create(
                    type: AnomalyType::OUTLIER_DETECTION,
                    featureType: $feature->type,
                    anomalyScore: $anomalyScore,
                    description: "Statistical outlier detected in {$feature->name}: value={$feature->value}, bounds=[{$lowerBound}, {$upperBound}]",
                    features: [$feature],
                    evidence: [
                        'value' => $feature->value,
                        'q1' => $q1,
                        'q3' => $q3,
                        'iqr' => $iqr,
                        'lower_bound' => $lowerBound,
                        'upper_bound' => $upperBound,
                        'sample_size' => count($history),
                    ]
                ),
            ];
        }

        return [];
    }

    /**
     * Detect trend-based anomalies using moving averages
     */
    private function detectTrendAnomalies(Feature $feature): array
    {
        $history = $this->getFeatureHistory($feature->type, $feature->name);

        if (count($history) < 10) {
            return [];
        }

        // Calculate short-term and long-term moving averages
        $shortWindow = min(5, count($history) / 2);
        $longWindow = count($history);

        $shortMA = $this->calculateMovingAverage($history, $shortWindow);
        $longMA = $this->calculateMovingAverage($history, $longWindow);

        // Detect sudden changes in trend
        if ($shortMA > 0 && $longMA > 0) {
            $trendRatio = $shortMA / $longMA;

            // Significant upward trend
            if ($trendRatio > 2.0) {
                return [
                    AnomalyDetection::create(
                        type: AnomalyType::BEHAVIORAL_DRIFT,
                        featureType: $feature->type,
                        anomalyScore: min(1.0, ($trendRatio - 1.0) / 2.0),
                        description: "Upward trend anomaly in {$feature->name}: short MA={$shortMA}, long MA={$longMA}",
                        features: [$feature],
                        evidence: [
                            'short_ma' => $shortMA,
                            'long_ma' => $longMA,
                            'trend_ratio' => $trendRatio,
                            'direction' => 'upward',
                        ]
                    ),
                ];
            }
            // Significant downward trend
            elseif ($trendRatio < 0.5) {
                return [
                    AnomalyDetection::create(
                        type: AnomalyType::BEHAVIORAL_DRIFT,
                        featureType: $feature->type,
                        anomalyScore: min(1.0, (1.0 - $trendRatio) / 0.5),
                        description: "Downward trend anomaly in {$feature->name}: short MA={$shortMA}, long MA={$longMA}",
                        features: [$feature],
                        evidence: [
                            'short_ma' => $shortMA,
                            'long_ma' => $longMA,
                            'trend_ratio' => $trendRatio,
                            'direction' => 'downward',
                        ]
                    ),
                ];
            }
        }

        return [];
    }

    /**
     * Detect frequency spikes
     */
    private function detectFrequencySpikes(Feature $feature, ?Baseline $baseline): array
    {
        if (! str_contains($feature->name, 'rate') && ! str_contains($feature->name, 'frequency')) {
            return [];
        }

        $history = $this->getFeatureHistory($feature->type, $feature->name);

        if (count($history) < 5) {
            return [];
        }

        $recentAverage = $this->calculateMovingAverage($history, min(5, count($history)));
        $historicalAverage = $this->calculateMovingAverage($history, count($history));

        // Detect spikes
        if ($recentAverage > 0 && $historicalAverage > 0) {
            $spikeRatio = $recentAverage / $historicalAverage;

            if ($spikeRatio > 3.0) {
                return [
                    AnomalyDetection::frequencySpike(
                        currentRate: $recentAverage,
                        baseline: $historicalAverage,
                        threshold: 3.0
                    ),
                ];
            }
        }

        return [];
    }

    /**
     * Detect pattern deviations
     */
    private function detectPatternDeviations(Feature $feature, ?Baseline $baseline): array
    {
        $history = $this->getFeatureHistory($feature->type, $feature->name);

        if (count($history) < $this->minSampleSize) {
            return [];
        }

        // Calculate coefficient of variation for pattern stability
        $mean = array_sum($history) / count($history);
        $variance = array_sum(array_map(fn ($x) => pow($x - $mean, 2), $history)) / count($history);
        $stdDev = sqrt($variance);

        $coefficientOfVariation = $mean > 0 ? $stdDev / $mean : 0.0;

        // High variation indicates pattern instability
        if ($coefficientOfVariation > 0.5) {
            $anomalyScore = min(1.0, $coefficientOfVariation / 1.0);

            return [
                AnomalyDetection::patternDeviation(
                    featureType: $feature->type,
                    pattern: $feature->name,
                    deviationScore: $anomalyScore,
                    features: [$feature]
                ),
            ];
        }

        return [];
    }

    /**
     * Record feature for historical analysis
     */
    private function recordFeature(Feature $feature): void
    {
        $key = $feature->type->value . ':' . $feature->name;

        if (! isset($this->featureHistory[$key])) {
            $this->featureHistory[$key] = [];
        }

        $this->featureHistory[$key][] = $feature->value;

        // Limit history size to prevent memory issues
        if (count($this->featureHistory[$key]) > 1000) {
            array_shift($this->featureHistory[$key]);
        }
    }

    /**
     * Get feature history for analysis
     */
    private function getFeatureHistory(FeatureType $type, string $name): array
    {
        $key = $type->value . ':' . $name;

        return $this->featureHistory[$key] ?? [];
    }

    /**
     * Calculate moving average
     */
    private function calculateMovingAverage(array $values, int $window): float
    {
        if (empty($values) || $window <= 0) {
            return 0.0;
        }

        $window = min($window, count($values));
        $recent = array_slice($values, -$window);

        return array_sum($recent) / count($recent);
    }

    public function updateModel(array $features): void
    {
        // For statistical detector, just record the features for baseline calculation
        foreach ($features as $feature) {
            if ($feature instanceof Feature) {
                $this->recordFeature($feature);
            }
        }
    }

    public function getConfiguration(): array
    {
        return [
            'enabled' => $this->enabled,
            'confidence_threshold' => $this->confidenceThreshold,
            'z_score_threshold' => $this->zScoreThreshold,
            'extreme_z_score_threshold' => $this->extremeZScoreThreshold,
            'min_sample_size' => $this->minSampleSize,
            'enable_outlier_detection' => $this->enableOutlierDetection,
            'enable_trend_analysis' => $this->enableTrendAnalysis,
            'supported_behavior_types' => array_map(fn ($type) => $type->value, $this->getSupportedFeatureTypes()),
            'feature_history_size' => array_sum(array_map('count', $this->featureHistory)),
        ];
    }

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

    public function getConfidenceThreshold(): float
    {
        return $this->confidenceThreshold;
    }

    public function setConfidenceThreshold(float $threshold): void
    {
        $this->confidenceThreshold = max(0.0, min(1.0, $threshold));
    }

    public function getExpectedProcessingTime(): int
    {
        return 25; // milliseconds
    }

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