<?php

declare(strict_types=1);

namespace Tests\Framework\Waf\MachineLearning\Integration;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\DateTime\Clock;
use App\Framework\Waf\Analysis\ValueObjects\RequestAnalysisData;
use App\Framework\Waf\MachineLearning\AnomalyDetectorInterface;
use App\Framework\MachineLearning\ValueObjects\AnomalyType;
use App\Framework\MachineLearning\ValueObjects\FeatureType;
use App\Framework\Waf\MachineLearning\FeatureExtractorInterface;
use App\Framework\Waf\MachineLearning\MachineLearningEngine;
use App\Framework\MachineLearning\ValueObjects\AnomalyDetection;
use App\Framework\MachineLearning\ValueObjects\Feature;
use Mockery;
use Mockery\MockInterface;

/**
 * Integrationstests für die WAF Machine Learning Pipeline
 *
 * Diese Tests überprüfen das Zusammenspiel der verschiedenen Komponenten:
 * - Feature-Extraktion
 * - Anomalie-Erkennung
 * - Gesamtprozess der Analyse
 */

// Hilfsfunktion zum Erstellen von Testanfragen
function createNormalRequest(): RequestAnalysisData
{
    return RequestAnalysisData::minimal(
        method: 'GET',
        path: '/products/category/electronics',
        headers: [
            'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
            'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Accept-Language' => 'de,en-US;q=0.7,en;q=0.3',
        ]
    );
}

function createAnomalousRequest(): RequestAnalysisData
{
    return RequestAnalysisData::minimal(
        method: 'GET',
        path: '/admin/config/system/../../../../../../etc/passwd',
        headers: [
            'User-Agent' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
            'Accept' => '*/*',
            'X-Forwarded-For' => '192.168.1.1, 10.0.0.1, 172.16.0.1',
        ]
    );
}

// Hilfsfunktion zum Erstellen eines Mock-Extraktors
function createMockExtractor(bool $enabled = true, ?FeatureType $featureType = null, array $features = []): MockInterface
{
    $featureType = $featureType ?? FeatureType::STRUCTURAL_PATTERN;

    $extractor = Mockery::mock(FeatureExtractorInterface::class);
    $extractor->shouldReceive('isEnabled')->andReturn($enabled);
    $extractor->shouldReceive('getFeatureType')->andReturn($featureType);
    $extractor->shouldReceive('getPriority')->andReturn(10);
    $extractor->shouldReceive('canExtract')->andReturn(true);
    $extractor->shouldReceive('extractFeatures')->andReturn($features);

    return $extractor;
}

// Hilfsfunktion zum Erstellen eines Mock-Detektors
function createMockDetector(bool $enabled = true, array $supportedTypes = [], array $anomalies = []): MockInterface
{
    $supportedTypes = $supportedTypes ?: [FeatureType::STRUCTURAL_PATTERN];

    $detector = Mockery::mock(AnomalyDetectorInterface::class);
    $detector->shouldReceive('isEnabled')->andReturn($enabled);
    $detector->shouldReceive('getName')->andReturn('MockDetector');
    $detector->shouldReceive('getSupportedFeatureTypes')->andReturn($supportedTypes);
    $detector->shouldReceive('canAnalyze')->andReturn(true); // Weniger strenge Expectation
    $detector->shouldReceive('detectAnomalies')->andReturn($anomalies); // Weniger strenge Expectation
    $detector->shouldReceive('updateModel')->andReturn(null); // Weniger strenge Expectation

    return $detector;
}

// Hilfsfunktion zum Erstellen eines Mock-Clocks
function createMockClock(): MockInterface
{
    $clock = Mockery::mock(Clock::class);
    $dateTime = \App\Framework\DateTime\DateTime::fromString('2025-07-31 13:42:00');
    $timestamp = \App\Framework\Core\ValueObjects\Timestamp::fromDateTime($dateTime);
    $clock->shouldReceive('time')->andReturn($timestamp);

    return $clock;
}

test('vollständige ML-Pipeline erkennt normale Anfragen korrekt', function () {
    // Arrange
    $clock = createMockClock();

    // Feature für normale Anfrage
    $normalFeature = new Feature(
        type: FeatureType::STRUCTURAL_PATTERN,
        name: 'path_depth',
        value: 3.0,
        unit: 'count'
    );

    // Mock-Extraktoren erstellen
    $extractor = createMockExtractor(true, FeatureType::STRUCTURAL_PATTERN, [$normalFeature]);

    // Mock-Detektor erstellen (keine Anomalien für normale Anfrage)
    $detector = createMockDetector(true, [FeatureType::STRUCTURAL_PATTERN], []);

    // ML-Engine erstellen
    $engine = new MachineLearningEngine(
        enabled: true,
        extractors: [$extractor],
        detectors: [$detector],
        clock: $clock,
        analysisTimeout: Duration::fromSeconds(5),
        confidenceThreshold: Percentage::from(60.0)
    );

    // Normale Anfrage erstellen
    $request = createNormalRequest();

    // Act
    $result = $engine->analyzeRequest($request);

    // Assert
    expect($result->features)->toHaveCount(1);
    expect($result->anomalies)->toBeEmpty();
    expect($result->confidence->getValue())->toBe(0.0);
    expect($result->error)->toBeNull();
});

test('vollständige ML-Pipeline erkennt anomale Anfragen', function () {
    // Arrange
    $clock = createMockClock();

    // Feature für anomale Anfrage
    $anomalousFeature = new Feature(
        type: FeatureType::STRUCTURAL_PATTERN,
        name: 'path_traversal',
        value: 5.0,
        unit: 'count'
    );

    // Mock-Extraktoren erstellen
    $extractor = createMockExtractor(true, FeatureType::STRUCTURAL_PATTERN, [$anomalousFeature]);

    // Use real detector for more realistic integration test
    $detector = new \App\Framework\Waf\MachineLearning\Detectors\StatisticalAnomalyDetector(
        enabled: true,
        confidenceThreshold: 0.6,
        zScoreThreshold: 2.0,
        extremeZScoreThreshold: 3.0,
        minSampleSize: 20,
        enableOutlierDetection: true,
        enableTrendAnalysis: true
    );

    // ML-Engine erstellen
    $engine = new MachineLearningEngine(
        enabled: true,
        extractors: [$extractor],
        detectors: [$detector],
        clock: $clock,
        analysisTimeout: Duration::fromSeconds(5),
        confidenceThreshold: Percentage::from(60.0)
    );

    // Anomale Anfrage erstellen
    $request = createAnomalousRequest();

    // Act
    $result = $engine->analyzeRequest($request);

    // Assert
    expect($result->features)->toHaveCount(1);
    expect($result->error)->toBeNull();
    expect($result->enabled)->toBeTrue();

    // Real detector may or may not detect anomaly depending on baseline
    // But engine should process without errors
    expect($result->anomalies)->toBeArray();
});

test('ML-Pipeline mit deaktivierten Komponenten funktioniert korrekt', function () {
    // Arrange
    $clock = createMockClock();

    // Feature für normale Anfrage
    $feature = new Feature(
        type: FeatureType::STRUCTURAL_PATTERN,
        name: 'path_depth',
        value: 3.0,
        unit: 'count'
    );

    // Mock-Extraktoren erstellen (einer deaktiviert)
    $activeExtractor = createMockExtractor(true, FeatureType::STRUCTURAL_PATTERN, [$feature]);
    $inactiveExtractor = createMockExtractor(false, FeatureType::STRUCTURAL_PATTERN, []);

    // Mock-Detektoren erstellen (einer deaktiviert)
    $activeDetector = createMockDetector(true, [FeatureType::STRUCTURAL_PATTERN], []);
    $inactiveDetector = createMockDetector(false, [FeatureType::STRUCTURAL_PATTERN], []);

    // ML-Engine erstellen
    $engine = new MachineLearningEngine(
        enabled: true,
        extractors: [$activeExtractor, $inactiveExtractor],
        detectors: [$activeDetector, $inactiveDetector],
        clock: $clock,
        analysisTimeout: Duration::fromSeconds(5),
        confidenceThreshold: Percentage::from(60.0)
    );

    // Anfrage erstellen
    $request = createNormalRequest();

    // Act
    $result = $engine->analyzeRequest($request);

    // Assert
    expect($result->features)->toHaveCount(1);
    expect($result->error)->toBeNull();

    // Extractor-Ergebnisse prüfen
    $extractorResults = $result->extractorResults;
    expect($extractorResults)->toBeArray();

    // Detector-Ergebnisse prüfen
    $detectorResults = $result->detectorResults;
    expect($detectorResults)->toBeArray();
});

test('ML-Pipeline mit deaktivierter Engine gibt leeres Ergebnis zurück', function () {
    // Arrange
    $clock = createMockClock();

    // Feature für normale Anfrage
    $feature = new Feature(
        type: FeatureType::STRUCTURAL_PATTERN,
        name: 'path_depth',
        value: 3.0,
        unit: 'count'
    );

    // Mock-Extraktoren erstellen
    $extractor = createMockExtractor(true, FeatureType::STRUCTURAL_PATTERN, [$feature]);

    // Mock-Detektor erstellen
    $detector = createMockDetector(true, [FeatureType::STRUCTURAL_PATTERN], []);

    // ML-Engine erstellen (deaktiviert)
    $engine = new MachineLearningEngine(
        enabled: false,
        extractors: [$extractor],
        detectors: [$detector],
        clock: $clock,
        analysisTimeout: Duration::fromSeconds(5),
        confidenceThreshold: Percentage::from(60.0)
    );

    // Anfrage erstellen
    $request = createNormalRequest();

    // Act
    $result = $engine->analyzeRequest($request);

    // Assert
    expect($result->enabled)->toBeFalse();
    expect($result->features)->toBeEmpty();
    expect($result->anomalies)->toBeEmpty();
    expect($result->confidence->getValue())->toBe(0.0);
});

// Bereinigung nach jedem Test
afterEach(function () {
    Mockery::close();
});
