<?php

declare(strict_types=1);

namespace App\Framework\Database\NPlusOneDetection\MachineLearning;

use App\Framework\MachineLearning\Core\AnomalyDetectorInterface;
use App\Framework\MachineLearning\Core\FeatureExtractorInterface;
use App\Framework\MachineLearning\ValueObjects\AnomalyDetection;
use App\Framework\MachineLearning\ValueObjects\Feature;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\DateTime\Clock;
use App\Framework\Database\NPlusOneDetection\QueryExecutionContext;

/**
 * Machine Learning engine for N+1 query detection
 *
 * Orchestrates feature extraction and anomaly detection
 * for identifying N+1 query patterns using ML algorithms
 */
final readonly class NPlusOneDetectionEngine
{
    /**
     * @param array<FeatureExtractorInterface> $extractors
     * @param array<AnomalyDetectorInterface> $detectors
     */
    public function __construct(
        private bool $enabled,
        private array $extractors,
        private array $detectors,
        private Clock $clock,
        private Duration $analysisTimeout,
        private Percentage $confidenceThreshold
    ) {
    }

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

    public function getConfiguration(): array
    {
        return [
            'enabled' => $this->enabled,
            'extractor_count' => count($this->extractors),
            'detector_count' => count($this->detectors),
            'analysis_timeout_ms' => $this->analysisTimeout->toMilliseconds(),
            'confidence_threshold' => $this->confidenceThreshold->value,
        ];
    }

    /**
     * Analyze query execution context for N+1 patterns
     *
     * @return array{
     *     success: bool,
     *     features: array<Feature>,
     *     anomalies: array<AnomalyDetection>,
     *     analysis_time_ms: float,
     *     overall_confidence: float,
     *     extractor_results: array,
     *     detector_results: array,
     *     error: string|null
     * }
     */
    public function analyzeQueryContext(QueryExecutionContext $context): array
    {
        if (!$this->enabled) {
            return $this->createDisabledResult();
        }

        $startTime = $this->clock->time();
        $allFeatures = [];
        $allAnomalies = [];
        $extractorResults = [];
        $detectorResults = [];

        try {
            // Phase 1: Feature Extraction
            foreach ($this->extractors as $extractor) {
                if (!$extractor->isEnabled() || !$extractor->canExtract($context)) {
                    continue;
                }

                $extractorStart = $this->clock->time();
                $features = $extractor->extractFeatures($context);
                $extractorDuration = Duration::between($extractorStart, $this->clock->time());

                $allFeatures = array_merge($allFeatures, $features);

                $extractorResults[] = [
                    'extractor' => get_class($extractor),
                    'feature_count' => count($features),
                    'execution_time_ms' => $extractorDuration->toMilliseconds(),
                    'success' => true,
                ];

                if ($this->hasExceededTimeout($startTime)) {
                    break;
                }
            }

            // Phase 2: Anomaly Detection
            foreach ($this->detectors as $detector) {
                if (!$detector->isEnabled() || !$detector->canAnalyze($allFeatures)) {
                    continue;
                }

                $detectorStart = $this->clock->time();
                $anomalies = $detector->detectAnomalies($allFeatures, baseline: null);
                $detectorDuration = Duration::between($detectorStart, $this->clock->time());

                $allAnomalies = array_merge($allAnomalies, $anomalies);

                $detectorResults[] = [
                    'detector' => $detector->getName(),
                    'anomaly_count' => count($anomalies),
                    'execution_time_ms' => $detectorDuration->toMilliseconds(),
                    'success' => true,
                ];

                if ($this->hasExceededTimeout($startTime)) {
                    break;
                }
            }

            // Phase 3: Filter by confidence threshold
            $highConfidenceAnomalies = $this->filterByConfidence($allAnomalies);

            // Calculate overall confidence
            $overallConfidence = $this->calculateOverallConfidence($highConfidenceAnomalies);

            $totalDuration = Duration::between($startTime, $this->clock->time());

            return [
                'success' => true,
                'features' => $allFeatures,
                'anomalies' => $highConfidenceAnomalies,
                'analysis_time_ms' => $totalDuration->toMilliseconds(),
                'overall_confidence' => $overallConfidence,
                'extractor_results' => $extractorResults,
                'detector_results' => $detectorResults,
                'error' => null,
            ];
        } catch (\Throwable $e) {
            return [
                'success' => false,
                'features' => $allFeatures,
                'anomalies' => [],
                'analysis_time_ms' => Duration::between($startTime, $this->clock->time())->toMilliseconds(),
                'overall_confidence' => 0.0,
                'extractor_results' => $extractorResults,
                'detector_results' => $detectorResults,
                'error' => $e->getMessage(),
            ];
        }
    }

    private function createDisabledResult(): array
    {
        return [
            'success' => false,
            'features' => [],
            'anomalies' => [],
            'analysis_time_ms' => 0.0,
            'overall_confidence' => 0.0,
            'extractor_results' => [],
            'detector_results' => [],
            'error' => 'Engine is disabled',
        ];
    }

    private function hasExceededTimeout(int $startTime): bool
    {
        $elapsed = Duration::between($startTime, $this->clock->time());
        return $elapsed->toMilliseconds() > $this->analysisTimeout->toMilliseconds();
    }

    /**
     * @param array<AnomalyDetection> $anomalies
     * @return array<AnomalyDetection>
     */
    private function filterByConfidence(array $anomalies): array
    {
        return array_filter(
            $anomalies,
            fn(AnomalyDetection $anomaly) => $anomaly->confidence->value >= $this->confidenceThreshold->value
        );
    }

    /**
     * @param array<AnomalyDetection> $anomalies
     */
    private function calculateOverallConfidence(array $anomalies): float
    {
        if (empty($anomalies)) {
            return 0.0;
        }

        $totalConfidence = array_reduce(
            $anomalies,
            fn(float $sum, AnomalyDetection $anomaly) => $sum + $anomaly->confidence->value,
            0.0
        );

        return $totalConfidence / count($anomalies);
    }
}
