<?php

declare(strict_types=1);

namespace App\Framework\Queue\MachineLearning;

use App\Framework\Queue\MachineLearning\ValueObjects\JobExecutionSequence;
use App\Framework\Queue\MachineLearning\ValueObjects\JobFeatures;

/**
 * Job Feature Extractor - Extract 8 Behavioral Features from Job Execution Sequences
 *
 * Analyzes job execution patterns and extracts normalized features
 * for machine learning-based anomaly detection.
 *
 * Features extracted:
 * 1. execution_time_variance - Execution time stability
 * 2. memory_usage_pattern - Memory consumption patterns
 * 3. retry_frequency - Retry attempt frequency
 * 4. failure_rate - Job failure percentage
 * 5. queue_depth_correlation - Queue workload impact
 * 6. dependency_chain_complexity - Execution dependency complexity
 * 7. payload_size_anomaly - Unusual payload sizes
 * 8. execution_timing_regularity - Timing consistency
 *
 * All features normalized to 0.0-1.0 range.
 */
final readonly class JobFeatureExtractor
{
    /**
     * @param float $minConfidence Minimum confidence threshold for feature extraction (0.0-1.0)
     */
    public function __construct(
        private float $minConfidence = 0.6
    ) {
        if ($minConfidence < 0.0 || $minConfidence > 1.0) {
            throw new \InvalidArgumentException('minConfidence must be between 0.0 and 1.0');
        }
    }

    /**
     * Extract features from job execution sequence
     */
    public function extract(JobExecutionSequence $sequence): JobFeatures
    {
        if ($sequence->isEmpty()) {
            return JobFeatures::zero();
        }

        return new JobFeatures(
            executionTimeVariance: $this->extractExecutionTimeVariance($sequence),
            memoryUsagePattern: $this->extractMemoryUsagePattern($sequence),
            retryFrequency: $this->extractRetryFrequency($sequence),
            failureRate: $this->extractFailureRate($sequence),
            queueDepthCorrelation: $this->extractQueueDepthCorrelation($sequence),
            dependencyChainComplexity: $this->extractDependencyChainComplexity($sequence),
            payloadSizeAnomaly: $this->extractPayloadSizeAnomaly($sequence),
            executionTimingRegularity: $this->extractExecutionTimingRegularity($sequence)
        );
    }

    /**
     * Feature 1: Execution Time Variance (0.0-1.0)
     *
     * Measures stability of execution times.
     * Higher values indicate inconsistent execution (possible performance issues).
     *
     * Calculation:
     * - Coefficient of variation (CV) = standard_deviation / mean
     * - Normalized using sigmoid: 1 / (1 + exp(-k * (CV - threshold)))
     */
    private function extractExecutionTimeVariance(JobExecutionSequence $sequence): float
    {
        if ($sequence->count() < 2) {
            return 0.0;
        }

        $mean = $sequence->getAverageExecutionTime();
        if ($mean === 0.0) {
            return 0.0;
        }

        $variance = $sequence->getExecutionTimeVariance();
        $stdDev = sqrt($variance);

        // Coefficient of Variation
        $cv = $stdDev / $mean;

        // Normalize using sigmoid (threshold at CV = 0.5)
        return $this->normalize($cv, 0.0, 2.0);
    }

    /**
     * Feature 2: Memory Usage Pattern (0.0-1.0)
     *
     * Detects anomalous memory consumption patterns.
     * Higher values indicate unusual memory behavior.
     *
     * Calculation:
     * - Memory variance relative to average
     * - Combined with memory growth trend
     */
    private function extractMemoryUsagePattern(JobExecutionSequence $sequence): float
    {
        if ($sequence->count() < 2) {
            return 0.0;
        }

        $mean = $sequence->getAverageMemoryUsage();
        if ($mean === 0.0) {
            return 0.0;
        }

        $variance = $sequence->getMemoryUsageVariance();
        $stdDev = sqrt($variance);

        // Coefficient of Variation for memory
        $cv = $stdDev / $mean;

        // Check for memory growth trend (leak indicator)
        $growthTrend = $this->calculateMemoryGrowthTrend($sequence);

        // Combine CV and growth trend
        $memoryScore = ($cv * 0.6) + ($growthTrend * 0.4);

        return $this->normalize($memoryScore, 0.0, 1.5);
    }

    /**
     * Feature 3: Retry Frequency (0.0-1.0)
     *
     * Measures how often jobs are retried.
     * Higher values indicate reliability issues.
     */
    private function extractRetryFrequency(JobExecutionSequence $sequence): float
    {
        return $sequence->getAverageRetryRate();
    }

    /**
     * Feature 4: Failure Rate (0.0-1.0)
     *
     * Percentage of failed job executions.
     * Direct indicator of job quality/reliability.
     */
    private function extractFailureRate(JobExecutionSequence $sequence): float
    {
        return $sequence->getFailureRate();
    }

    /**
     * Feature 5: Queue Depth Correlation (0.0-1.0)
     *
     * Measures correlation between queue depth and execution time.
     * Higher values indicate performance degradation under load.
     *
     * Calculation:
     * - Pearson correlation between queue depth and execution time
     * - Normalized to 0-1 (abs correlation)
     */
    private function extractQueueDepthCorrelation(JobExecutionSequence $sequence): float
    {
        if ($sequence->count() < 3) {
            return 0.0;
        }

        $executions = $sequence->all();

        // Extract queue depths and execution times
        $queueDepths = [];
        $executionTimes = [];

        foreach ($executions as $exec) {
            $queueDepths[] = (float) $exec->queueDepth;
            $executionTimes[] = $exec->executionTimeMs;
        }

        // Calculate Pearson correlation
        $correlation = $this->calculatePearsonCorrelation($queueDepths, $executionTimes);

        // Return absolute correlation (0-1)
        return abs($correlation);
    }

    /**
     * Feature 6: Dependency Chain Complexity (0.0-1.0)
     *
     * Estimates complexity of job dependencies.
     * Higher values indicate complex execution patterns.
     *
     * Heuristic based on:
     * - Number of unique queues (more queues = more complex dependencies)
     * - Execution frequency variation
     */
    private function extractDependencyChainComplexity(JobExecutionSequence $sequence): float
    {
        if ($sequence->isEmpty()) {
            return 0.0;
        }

        $uniqueQueues = count($sequence->getUniqueQueues());

        // More queues = potentially more complex dependencies
        $queueComplexity = $this->normalize((float) $uniqueQueues, 1.0, 10.0);

        // Execution frequency variation as complexity indicator
        $frequencyVariation = $this->calculateExecutionFrequencyVariation($sequence);

        // Combine indicators
        return ($queueComplexity * 0.5) + ($frequencyVariation * 0.5);
    }

    /**
     * Feature 7: Payload Size Anomaly (0.0-1.0)
     *
     * Detects unusual payload sizes.
     * Higher values indicate data anomalies.
     *
     * Calculation:
     * - Coefficient of variation for payload sizes
     * - Outlier detection using IQR method
     */
    private function extractPayloadSizeAnomaly(JobExecutionSequence $sequence): float
    {
        if ($sequence->count() < 3) {
            return 0.0;
        }

        $executions = $sequence->all();

        // Extract payload sizes
        $payloadSizes = array_map(
            fn($exec) => (float) $exec->payloadSizeBytes,
            $executions
        );

        // Filter out zeros
        $payloadSizes = array_filter($payloadSizes, fn($size) => $size > 0);

        if (empty($payloadSizes)) {
            return 0.0;
        }

        // Calculate CV
        $mean = array_sum($payloadSizes) / count($payloadSizes);
        if ($mean === 0.0) {
            return 0.0;
        }

        $variance = 0.0;
        foreach ($payloadSizes as $size) {
            $variance += ($size - $mean) ** 2;
        }
        $variance /= count($payloadSizes);
        $stdDev = sqrt($variance);

        $cv = $stdDev / $mean;

        // Normalize CV
        return $this->normalize($cv, 0.0, 2.0);
    }

    /**
     * Feature 8: Execution Timing Regularity (0.0-1.0)
     *
     * Measures consistency of execution timing intervals.
     * Higher values indicate very regular patterns (possible automated/bot execution).
     *
     * Calculation:
     * - Calculate variance of inter-execution times
     * - Lower variance = higher regularity
     */
    private function extractExecutionTimingRegularity(JobExecutionSequence $sequence): float
    {
        if ($sequence->count() < 3) {
            return 0.0;
        }

        $executions = $sequence->all();

        // Calculate inter-execution times
        $intervals = [];
        for ($i = 1; $i < count($executions); $i++) {
            $prev = $executions[$i - 1];
            $curr = $executions[$i];

            $interval = $curr->createdAt->toFloat() - $prev->createdAt->toFloat();
            $intervals[] = $interval;
        }

        if (empty($intervals)) {
            return 0.0;
        }

        // Calculate variance of intervals
        $mean = array_sum($intervals) / count($intervals);
        if ($mean === 0.0) {
            return 0.0;
        }

        $variance = 0.0;
        foreach ($intervals as $interval) {
            $variance += ($interval - $mean) ** 2;
        }
        $variance /= count($intervals);

        // Coefficient of variation
        $stdDev = sqrt($variance);
        $cv = $stdDev / $mean;

        // Invert CV: low variance = high regularity
        // Perfect regularity (CV = 0) → 1.0
        // High variance (CV > 1) → 0.0
        return max(0.0, 1.0 - $cv);
    }

    /**
     * Calculate memory growth trend
     */
    private function calculateMemoryGrowthTrend(JobExecutionSequence $sequence): float
    {
        $executions = $sequence->all();
        if (count($executions) < 3) {
            return 0.0;
        }

        // Simple linear regression slope for memory usage
        $n = count($executions);
        $sumX = 0;
        $sumY = 0;
        $sumXY = 0;
        $sumX2 = 0;

        foreach ($executions as $i => $exec) {
            $x = (float) $i;
            $y = (float) $exec->memoryUsageBytes;

            $sumX += $x;
            $sumY += $y;
            $sumXY += $x * $y;
            $sumX2 += $x * $x;
        }

        $denominator = ($n * $sumX2) - ($sumX * $sumX);
        if ($denominator === 0.0) {
            return 0.0;
        }

        $slope = (($n * $sumXY) - ($sumX * $sumY)) / $denominator;

        // Normalize slope relative to average memory
        $avgMemory = $sequence->getAverageMemoryUsage();
        if ($avgMemory === 0.0) {
            return 0.0;
        }

        $normalizedSlope = abs($slope) / $avgMemory;

        return $this->normalize($normalizedSlope, 0.0, 0.5);
    }

    /**
     * Calculate Pearson correlation coefficient
     */
    private function calculatePearsonCorrelation(array $x, array $y): float
    {
        $n = count($x);
        if ($n === 0 || count($y) !== $n) {
            return 0.0;
        }

        $sumX = array_sum($x);
        $sumY = array_sum($y);
        $sumXY = 0;
        $sumX2 = 0;
        $sumY2 = 0;

        for ($i = 0; $i < $n; $i++) {
            $sumXY += $x[$i] * $y[$i];
            $sumX2 += $x[$i] * $x[$i];
            $sumY2 += $y[$i] * $y[$i];
        }

        $numerator = ($n * $sumXY) - ($sumX * $sumY);
        $denominator = sqrt((($n * $sumX2) - ($sumX * $sumX)) * (($n * $sumY2) - ($sumY * $sumY)));

        if ($denominator === 0.0) {
            return 0.0;
        }

        return $numerator / $denominator;
    }

    /**
     * Calculate execution frequency variation
     */
    private function calculateExecutionFrequencyVariation(JobExecutionSequence $sequence): float
    {
        if ($sequence->count() < 3) {
            return 0.0;
        }

        $executions = $sequence->all();

        // Calculate inter-execution times
        $intervals = [];
        for ($i = 1; $i < count($executions); $i++) {
            $prev = $executions[$i - 1];
            $curr = $executions[$i];

            $interval = $curr->createdAt->toFloat() - $prev->createdAt->toFloat();
            $intervals[] = $interval;
        }

        if (empty($intervals)) {
            return 0.0;
        }

        // Calculate CV
        $mean = array_sum($intervals) / count($intervals);
        if ($mean === 0.0) {
            return 0.0;
        }

        $variance = 0.0;
        foreach ($intervals as $interval) {
            $variance += ($interval - $mean) ** 2;
        }
        $variance /= count($intervals);
        $stdDev = sqrt($variance);

        $cv = $stdDev / $mean;

        return $this->normalize($cv, 0.0, 2.0);
    }

    /**
     * Normalize value to 0.0-1.0 range
     */
    private function normalize(float $value, float $min, float $max): float
    {
        if ($max === $min) {
            return 0.0;
        }

        $normalized = ($value - $min) / ($max - $min);

        return max(0.0, min(1.0, $normalized));
    }
}
