<?php

declare(strict_types=1);

namespace Tests\Framework\Database\NPlusOneDetection\MachineLearning\Extractors;

use App\Framework\Database\NPlusOneDetection\MachineLearning\Extractors\QueryFeatureExtractor;
use App\Framework\Database\NPlusOneDetection\QueryExecutionContext;
use App\Framework\MachineLearning\ValueObjects\FeatureType;
use App\Framework\Core\ValueObjects\Duration;

describe('QueryFeatureExtractor', function () {
    it('is enabled by default', function () {
        $extractor = new QueryFeatureExtractor();

        expect($extractor->isEnabled())->toBeTrue();
    });

    it('can be disabled via constructor', function () {
        $extractor = new QueryFeatureExtractor(enabled: false);

        expect($extractor->isEnabled())->toBeFalse();
    });

    it('returns FREQUENCY as feature type', function () {
        $extractor = new QueryFeatureExtractor();

        expect($extractor->getFeatureType())->toBe(FeatureType::FREQUENCY);
    });

    it('has default priority of 10', function () {
        $extractor = new QueryFeatureExtractor();

        expect($extractor->getPriority())->toBe(10);
    });

    it('can extract from QueryExecutionContext', function () {
        $extractor = new QueryFeatureExtractor();
        $context = QueryExecutionContext::minimal();

        expect($extractor->canExtract($context))->toBeTrue();
    });

    it('cannot extract from non-QueryExecutionContext', function () {
        $extractor = new QueryFeatureExtractor();

        expect($extractor->canExtract(['not' => 'context']))->toBeFalse();
        expect($extractor->canExtract('string'))->toBeFalse();
        expect($extractor->canExtract(null))->toBeFalse();
    });

    it('extracts 8 features from minimal context', function () {
        $extractor = new QueryFeatureExtractor();
        $context = QueryExecutionContext::minimal(
            queryCount: 10,
            durationMs: 100.0,
            uniqueQueries: 5
        );

        $features = $extractor->extractFeatures($context);

        expect($features)->toHaveCount(8);

        // Verify all expected features are present
        $featureNames = array_map(fn($f) => $f->name, $features);

        expect($featureNames)->toContain('query_frequency');
        expect($featureNames)->toContain('query_repetition_rate');
        expect($featureNames)->toContain('avg_query_execution_time');
        expect($featureNames)->toContain('timing_pattern_regularity');
        expect($featureNames)->toContain('avg_query_complexity');
        expect($featureNames)->toContain('avg_join_count');
        expect($featureNames)->toContain('loop_execution_detected');
        expect($featureNames)->toContain('query_similarity_score');
    });

    it('calculates query frequency correctly', function () {
        $extractor = new QueryFeatureExtractor();

        // 10 queries in 1 second = 10 queries/second
        $context = QueryExecutionContext::minimal(
            queryCount: 10,
            durationMs: 1000.0,
            uniqueQueries: 5
        );

        $features = $extractor->extractFeatures($context);
        $frequencyFeature = array_values(array_filter(
            $features,
            fn($f) => $f->name === 'query_frequency'
        ))[0];

        expect($frequencyFeature->value)->toBe(10.0);
        expect($frequencyFeature->unit)->toBe('queries/second');
    });

    it('calculates query repetition rate correctly', function () {
        $extractor = new QueryFeatureExtractor();

        // 10 total queries, 3 unique = 70% repetition rate
        $context = QueryExecutionContext::minimal(
            queryCount: 10,
            durationMs: 100.0,
            uniqueQueries: 3
        );

        $features = $extractor->extractFeatures($context);
        $repetitionFeature = array_values(array_filter(
            $features,
            fn($f) => $f->name === 'query_repetition_rate'
        ))[0];

        expect($repetitionFeature->value)->toBe(70.0);
        expect($repetitionFeature->unit)->toBe('percentage');
    });

    it('calculates average execution time correctly', function () {
        $extractor = new QueryFeatureExtractor();

        // 100ms total for 10 queries = 10ms average
        $context = QueryExecutionContext::minimal(
            queryCount: 10,
            durationMs: 100.0,
            uniqueQueries: 5
        );

        $features = $extractor->extractFeatures($context);
        $avgTimeFeature = array_values(array_filter(
            $features,
            fn($f) => $f->name === 'avg_query_execution_time'
        ))[0];

        expect($avgTimeFeature->value)->toBe(10.0);
        expect($avgTimeFeature->unit)->toBe('milliseconds');
    });

    it('detects loop execution pattern', function () {
        $extractor = new QueryFeatureExtractor();

        $context = new QueryExecutionContext(
            queryCount: 10,
            duration: Duration::fromMilliseconds(100),
            uniqueQueryHashes: ['hash1', 'hash2'],
            queryTimings: array_fill(0, 10, 10.0),
            queryComplexityScores: array_fill(0, 10, 0.5),
            totalJoinCount: 5,
            executedInLoop: true,
            loopDepth: 2
        );

        $features = $extractor->extractFeatures($context);
        $loopFeature = array_values(array_filter(
            $features,
            fn($f) => $f->name === 'loop_execution_detected'
        ))[0];

        expect($loopFeature->value)->toBe(1.0);
        expect($loopFeature->unit)->toBe('binary');
        expect($loopFeature->metadata['loop_depth'])->toBe(2);
    });

    it('calculates high query similarity for N+1 pattern', function () {
        $extractor = new QueryFeatureExtractor();

        // 20 total queries, only 2 unique = 90% similarity
        $context = QueryExecutionContext::minimal(
            queryCount: 20,
            durationMs: 200.0,
            uniqueQueries: 2
        );

        $features = $extractor->extractFeatures($context);
        $similarityFeature = array_values(array_filter(
            $features,
            fn($f) => $f->name === 'query_similarity_score'
        ))[0];

        expect($similarityFeature->value)->toBe(0.9); // 1.0 - (2/20)
        expect($similarityFeature->unit)->toBe('score');
    });

    it('calculates timing pattern regularity from consistent timings', function () {
        $extractor = new QueryFeatureExtractor();

        // Very consistent timings (low CV = high regularity)
        $context = new QueryExecutionContext(
            queryCount: 10,
            duration: Duration::fromMilliseconds(100),
            uniqueQueryHashes: ['hash1'],
            queryTimings: [10.0, 10.1, 9.9, 10.0, 10.2, 9.8, 10.0, 10.1, 9.9, 10.0],
            queryComplexityScores: array_fill(0, 10, 0.5),
            totalJoinCount: 0,
            executedInLoop: true
        );

        $features = $extractor->extractFeatures($context);
        $regularityFeature = array_values(array_filter(
            $features,
            fn($f) => $f->name === 'timing_pattern_regularity'
        ))[0];

        // High regularity expected (>0.9)
        expect($regularityFeature->value)->toBeGreaterThan(0.9);
        expect($regularityFeature->unit)->toBe('score');
    });

    it('calculates low timing pattern regularity from irregular timings', function () {
        $extractor = new QueryFeatureExtractor();

        // Very irregular timings (high CV = low regularity)
        $context = new QueryExecutionContext(
            queryCount: 10,
            duration: Duration::fromMilliseconds(500),
            uniqueQueryHashes: ['hash1', 'hash2', 'hash3'],
            queryTimings: [5.0, 50.0, 10.0, 100.0, 8.0, 75.0, 12.0, 90.0, 6.0, 80.0],
            queryComplexityScores: array_fill(0, 10, 0.5),
            totalJoinCount: 0,
            executedInLoop: false
        );

        $features = $extractor->extractFeatures($context);
        $regularityFeature = array_values(array_filter(
            $features,
            fn($f) => $f->name === 'timing_pattern_regularity'
        ))[0];

        // Low regularity expected (<0.5)
        expect($regularityFeature->value)->toBeLessThan(0.5);
    });

    it('calculates average query complexity', function () {
        $extractor = new QueryFeatureExtractor();

        $context = new QueryExecutionContext(
            queryCount: 5,
            duration: Duration::fromMilliseconds(50),
            uniqueQueryHashes: ['hash1', 'hash2'],
            queryTimings: array_fill(0, 5, 10.0),
            queryComplexityScores: [0.2, 0.4, 0.6, 0.8, 1.0],
            totalJoinCount: 10,
            executedInLoop: false
        );

        $features = $extractor->extractFeatures($context);
        $complexityFeature = array_values(array_filter(
            $features,
            fn($f) => $f->name === 'avg_query_complexity'
        ))[0];

        // Average of [0.2, 0.4, 0.6, 0.8, 1.0] = 0.6
        expect($complexityFeature->value)->toBe(0.6);
        expect($complexityFeature->unit)->toBe('score');
    });

    it('calculates average join count', function () {
        $extractor = new QueryFeatureExtractor();

        $context = new QueryExecutionContext(
            queryCount: 10,
            duration: Duration::fromMilliseconds(100),
            uniqueQueryHashes: ['hash1'],
            queryTimings: array_fill(0, 10, 10.0),
            queryComplexityScores: array_fill(0, 10, 0.5),
            totalJoinCount: 30, // 30 joins across 10 queries = 3.0 average
            executedInLoop: false
        );

        $features = $extractor->extractFeatures($context);
        $joinFeature = array_values(array_filter(
            $features,
            fn($f) => $f->name === 'avg_join_count'
        ))[0];

        expect($joinFeature->value)->toBe(3.0);
        expect($joinFeature->unit)->toBe('count');
    });

    it('handles zero queries gracefully', function () {
        $extractor = new QueryFeatureExtractor();

        $context = new QueryExecutionContext(
            queryCount: 0,
            duration: Duration::fromMilliseconds(0),
            uniqueQueryHashes: [],
            queryTimings: [],
            queryComplexityScores: [],
            totalJoinCount: 0,
            executedInLoop: false
        );

        $features = $extractor->extractFeatures($context);

        // Should return features with 0.0 values, not throw
        expect($features)->not->toBeEmpty();

        foreach ($features as $feature) {
            expect($feature->value)->toBeFloat();
        }
    });

    it('returns empty array for non-extractable data', function () {
        $extractor = new QueryFeatureExtractor();

        $features = $extractor->extractFeatures(['not' => 'context']);

        expect($features)->toBeEmpty();
    });

    it('includes metadata in features', function () {
        $extractor = new QueryFeatureExtractor();

        $context = QueryExecutionContext::minimal(
            queryCount: 5,
            durationMs: 50.0,
            uniqueQueries: 2
        );

        $features = $extractor->extractFeatures($context);
        $frequencyFeature = array_values(array_filter(
            $features,
            fn($f) => $f->name === 'query_frequency'
        ))[0];

        expect($frequencyFeature->metadata)->toHaveKey('query_count');
        expect($frequencyFeature->metadata)->toHaveKey('duration_seconds');
        expect($frequencyFeature->metadata['query_count'])->toBe(5);
    });
});
