<?php

declare(strict_types=1);

namespace App\Framework\MachineLearning\ValueObjects;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Core\ValueObjects\Timestamp;

/**
 * Represents a detected anomaly from machine learning analysis
 *
 * This value object is domain-agnostic and can represent anomalies detected
 * across any framework component:
 * - WAF: Malicious request patterns, attack signatures
 * - Query Optimization: N+1 query patterns, performance anomalies
 * - Performance Monitoring: Response time spikes, resource exhaustion
 * - Security Events: Authentication failures, privilege escalation
 *
 * Design Principles:
 * - Immutable value object (readonly)
 * - Rich domain modeling with confidence, risk, and severity scoring
 * - Factory methods for common anomaly types
 * - Self-contained evidence and metadata
 * - Framework-agnostic (uses generic FeatureType instead of domain-specific types)
 */
final readonly class AnomalyDetection
{
    /**
     * @param AnomalyType $type Type of anomaly detected
     * @param FeatureType $featureType Feature category this anomaly relates to
     * @param Percentage $confidence Detection confidence (0-100%)
     * @param float $anomalyScore Normalized anomaly score (0.0-1.0)
     * @param string $description Human-readable anomaly description
     * @param array<Feature> $features Features that contributed to detection
     * @param array<string, mixed> $evidence Supporting evidence for the detection
     * @param string|null $entityId Optional entity identifier (user, IP, session, etc.)
     * @param string|null $contextId Optional context identifier (request, query, transaction, etc.)
     * @param Timestamp|null $detectedAt When anomaly was detected
     * @param Duration|null $analysisWindow Time window used for analysis
     * @param array<string, mixed> $metadata Additional anomaly metadata
     */
    public function __construct(
        public AnomalyType $type,
        public FeatureType $featureType,
        public Percentage $confidence,
        public float $anomalyScore,
        public string $description,
        public array $features,
        public array $evidence,
        public ?string $entityId = null,
        public ?string $contextId = null,
        public ?Timestamp $detectedAt = null,
        public ?Duration $analysisWindow = null,
        public array $metadata = []
    ) {}

    /**
     * Create anomaly detection with automatic confidence calculation
     */
    public static function create(
        AnomalyType $type,
        FeatureType $featureType,
        float $anomalyScore,
        string $description,
        array $features = [],
        array $evidence = []
    ): self {
        // Calculate confidence based on anomaly score and feature consistency
        $baseConfidence = min($anomalyScore * 100, 100.0);

        // Adjust confidence based on feature agreement
        if (!empty($features)) {
            $featureAnomalyScores = array_map(
                fn(Feature $feature) => $feature->getAnomalyScore(),
                $features
            );

            $meanFeatureScore = array_sum($featureAnomalyScores) / count($featureAnomalyScores);
            $featureConsistency = 1.0 - (abs($anomalyScore - $meanFeatureScore) / max($anomalyScore, 0.01));

            $baseConfidence *= $featureConsistency;
        }

        $confidence = Percentage::from(max(0.0, min(100.0, $baseConfidence)));

        return new self(
            type: $type,
            featureType: $featureType,
            confidence: $confidence,
            anomalyScore: $anomalyScore,
            description: $description,
            features: $features,
            evidence: $evidence,
            detectedAt: Timestamp::fromFloat(microtime(true))
        );
    }

    /**
     * Create frequency spike anomaly
     */
    public static function frequencySpike(
        float $currentRate,
        float $baseline,
        float $threshold = 3.0,
        ?string $entityId = null
    ): self {
        $ratio = $baseline > 0 ? $currentRate / $baseline : $currentRate;
        $anomalyScore = min(($ratio - 1.0) / $threshold, 1.0);

        $anomaly = self::create(
            type: AnomalyType::FREQUENCY_SPIKE,
            featureType: FeatureType::FREQUENCY,
            anomalyScore: $anomalyScore,
            description: "Frequency spike detected: {$currentRate}/s (baseline: {$baseline}/s, ratio: " . round($ratio, 2) . "x)",
            evidence: [
                'current_rate' => $currentRate,
                'baseline_rate' => $baseline,
                'spike_ratio' => $ratio,
                'threshold' => $threshold,
            ]
        );

        return $entityId !== null ? $anomaly->withEntityId($entityId) : $anomaly;
    }

    /**
     * Create geographic anomaly
     */
    public static function geographicAnomaly(
        string $currentLocation,
        array $normalLocations,
        float $distance,
        ?string $entityId = null
    ): self {
        $anomalyScore = min($distance / 10000, 1.0); // Normalize by 10,000 km

        $anomaly = self::create(
            type: AnomalyType::GEOGRAPHIC_ANOMALY,
            featureType: FeatureType::GEOGRAPHIC_DISTRIBUTION,
            anomalyScore: $anomalyScore,
            description: "Geographic anomaly: access from {$currentLocation}, distance: " . round($distance) . "km from normal locations",
            evidence: [
                'current_location' => $currentLocation,
                'normal_locations' => $normalLocations,
                'distance_km' => $distance,
            ]
        );

        return $entityId !== null ? $anomaly->withEntityId($entityId) : $anomaly;
    }

    /**
     * Create pattern deviation anomaly
     */
    public static function patternDeviation(
        FeatureType $featureType,
        string $pattern,
        float $deviationScore,
        array $features = []
    ): self {
        return self::create(
            type: AnomalyType::UNUSUAL_PATTERN,
            featureType: $featureType,
            anomalyScore: $deviationScore,
            description: "Unusual pattern detected in {$featureType->getDescription()}: {$pattern}",
            features: $features,
            evidence: [
                'pattern' => $pattern,
                'deviation_score' => $deviationScore,
                'feature_count' => count($features),
            ]
        );
    }

    /**
     * Create statistical anomaly
     */
    public static function statisticalAnomaly(
        FeatureType $featureType,
        string $metric,
        float $value,
        float $expectedValue,
        float $standardDeviation,
        ?string $entityId = null
    ): self {
        $zScore = $standardDeviation > 0 ? abs($value - $expectedValue) / $standardDeviation : 0;
        $anomalyScore = min($zScore / 3.0, 1.0); // Normalize by 3 sigma

        $anomaly = self::create(
            type: AnomalyType::STATISTICAL_ANOMALY,
            featureType: $featureType,
            anomalyScore: $anomalyScore,
            description: "Statistical anomaly in {$metric}: value={$value}, expected={$expectedValue}, z-score=" . round($zScore, 2),
            evidence: [
                'metric' => $metric,
                'value' => $value,
                'expected_value' => $expectedValue,
                'standard_deviation' => $standardDeviation,
                'z_score' => $zScore,
            ]
        );

        return $entityId !== null ? $anomaly->withEntityId($entityId) : $anomaly;
    }

    /**
     * Add entity ID (user, IP, session, etc.)
     */
    public function withEntityId(string $entityId): self
    {
        return new self(
            type: $this->type,
            featureType: $this->featureType,
            confidence: $this->confidence,
            anomalyScore: $this->anomalyScore,
            description: $this->description,
            features: $this->features,
            evidence: $this->evidence,
            entityId: $entityId,
            contextId: $this->contextId,
            detectedAt: $this->detectedAt,
            analysisWindow: $this->analysisWindow,
            metadata: $this->metadata
        );
    }

    /**
     * Add context ID (request, query, transaction, etc.)
     */
    public function withContextId(string $contextId): self
    {
        return new self(
            type: $this->type,
            featureType: $this->featureType,
            confidence: $this->confidence,
            anomalyScore: $this->anomalyScore,
            description: $this->description,
            features: $this->features,
            evidence: $this->evidence,
            entityId: $this->entityId,
            contextId: $contextId,
            detectedAt: $this->detectedAt,
            analysisWindow: $this->analysisWindow,
            metadata: $this->metadata
        );
    }

    /**
     * Add analysis window
     */
    public function withAnalysisWindow(Duration $window): self
    {
        return new self(
            type: $this->type,
            featureType: $this->featureType,
            confidence: $this->confidence,
            anomalyScore: $this->anomalyScore,
            description: $this->description,
            features: $this->features,
            evidence: $this->evidence,
            entityId: $this->entityId,
            contextId: $this->contextId,
            detectedAt: $this->detectedAt,
            analysisWindow: $window,
            metadata: $this->metadata
        );
    }

    /**
     * Check if anomaly requires immediate action
     */
    public function requiresImmediateAction(): bool
    {
        return $this->type->requiresImmediateAction() &&
               $this->confidence->getValue() >= $this->type->getConfidenceThreshold() * 100;
    }

    /**
     * Get risk level
     */
    public function getRiskLevel(): string
    {
        $confidenceScore = $this->confidence->getValue() / 100.0;
        $combinedScore = ($this->anomalyScore + $confidenceScore) / 2.0;

        return match (true) {
            $combinedScore >= 0.8 => 'critical',
            $combinedScore >= 0.6 => 'high',
            $combinedScore >= 0.4 => 'medium',
            $combinedScore >= 0.2 => 'low',
            default => 'info'
        };
    }

    /**
     * Get recommended action
     */
    public function getRecommendedAction(): string
    {
        return $this->type->getRecommendedAction();
    }

    /**
     * Get severity score (0-100)
     */
    public function getSeverityScore(): float
    {
        $typeWeight = match ($this->type->getSeverityLevel()) {
            'high' => 0.9,
            'medium' => 0.6,
            'low' => 0.3,
            default => 0.5
        };

        $confidenceWeight = $this->confidence->getValue() / 100.0;
        $anomalyWeight = $this->anomalyScore;

        return ($typeWeight * 0.4 + $confidenceWeight * 0.3 + $anomalyWeight * 0.3) * 100;
    }

    /**
     * Convert to array for logging/storage
     */
    public function toArray(): array
    {
        return [
            'type' => $this->type->value,
            'feature_type' => $this->featureType->value,
            'confidence' => $this->confidence->getValue(),
            'anomaly_score' => $this->anomalyScore,
            'description' => $this->description,
            'entity_id' => $this->entityId,
            'context_id' => $this->contextId,
            'detected_at' => $this->detectedAt?->format('c'),
            'analysis_window_seconds' => $this->analysisWindow?->toSeconds(),
            'features' => array_map(fn(Feature $f) => $f->toArray(), $this->features),
            'evidence' => $this->evidence,
            'risk_level' => $this->getRiskLevel(),
            'severity_score' => $this->getSeverityScore(),
            'requires_immediate_action' => $this->requiresImmediateAction(),
            'recommended_action' => $this->getRecommendedAction(),
            'metadata' => $this->metadata,
        ];
    }

    /**
     * Create summary for dashboard/alerting
     */
    public function getSummary(): array
    {
        return [
            'id' => md5($this->type->value . $this->featureType->value . ($this->detectedAt?->format('c') ?? '')),
            'type' => $this->type->value,
            'description' => $this->description,
            'risk_level' => $this->getRiskLevel(),
            'confidence' => $this->confidence->getValue(),
            'entity_id' => $this->entityId,
            'detected_at' => $this->detectedAt?->format('c'),
            'requires_action' => $this->requiresImmediateAction(),
        ];
    }
}
