<?php

declare(strict_types=1);

namespace Tests\Framework\Waf\Feedback;

use App\Framework\Logging\Logger;
use App\Framework\Waf\DetectionCategory;
use App\Framework\Waf\DetectionSeverity;
use App\Framework\Waf\Feedback\FeedbackLearningService;
use App\Framework\Waf\Feedback\FeedbackService;
use App\Framework\Waf\MachineLearning\ValueObjects\ModelAdjustment;
use App\Framework\Waf\ValueObjects\Detection;
use PHPUnit\Framework\TestCase;

/**
 * Integration tests for the WAF feedback system
 */
class FeedbackIntegrationTest extends TestCase
{
    private TestClock $clock;

    private TestMachineLearningEngine $mlEngine;

    private InMemoryFeedbackRepository $repository;

    private Logger $logger;

    private FeedbackService $feedbackService;

    private FeedbackLearningService $learningService;

    protected function setUp(): void
    {
        $this->clock = new TestClock('2025-08-04 18:39:00');
        $this->mlEngine = new TestMachineLearningEngine();
        $this->repository = new InMemoryFeedbackRepository();
        $this->logger = $this->createMock(Logger::class);

        $this->feedbackService = new FeedbackService(
            $this->repository,
            $this->clock,
            $this->logger
        );

        $this->learningService = new FeedbackLearningService(
            $this->repository,
            $this->mlEngine,
            $this->clock,
            $this->logger,
            3, // Lower threshold for testing
            0.5 // Higher learning rate for testing
        );

        // Set up ML engine to return success for adjustments
        $this->mlEngine->withApplyFeedbackAdjustmentsResult([
            'success' => true,
            'applied_count' => 0,
            'failed_count' => 0,
            'results' => [],
        ]);
    }

    /**
     * Test the complete feedback loop:
     * 1. Submit feedback
     * 2. Learn from feedback
     * 3. Apply model adjustments
     */
    public function testCompleteFeedbackLoop(): void
    {
        // 1. Submit feedback
        $this->submitTestFeedback();

        // Verify feedback was stored
        $this->assertCount(5, $this->repository->getAllFeedback());

        // 2. Learn from feedback
        $learningResult = $this->learningService->learnFromFeedback();

        // Verify learning result
        $this->assertTrue($learningResult['success']);
        $this->assertGreaterThan(0, $learningResult['total_adjustments_applied']);

        // 3. Verify model adjustments were applied
        $receivedAdjustments = $this->mlEngine->getReceivedAdjustments();
        $this->assertNotEmpty($receivedAdjustments);

        // Verify SQL_INJECTION adjustments
        $sqlInjectionAdjustment = $this->findAdjustmentForCategory($receivedAdjustments, DetectionCategory::SQL_INJECTION);
        $this->assertNotNull($sqlInjectionAdjustment);

        // For false positives, threshold should be increased (positive adjustment)
        $this->assertGreaterThan(0, $sqlInjectionAdjustment->thresholdAdjustment->getValue());

        // For false positives, confidence should be decreased (negative adjustment)
        $this->assertLessThan(0, $sqlInjectionAdjustment->confidenceAdjustment->getValue());
    }

    /**
     * Test that severity adjustments are properly processed
     */
    public function testSeverityAdjustments(): void
    {
        // Submit severity adjustment feedback
        $this->feedbackService->submitSeverityAdjustment(
            'detection1',
            'test-user',
            'This should be higher severity',
            DetectionCategory::PATH_TRAVERSAL,
            DetectionSeverity::MEDIUM,
            DetectionSeverity::HIGH,
            ['test' => true]
        );

        $this->feedbackService->submitSeverityAdjustment(
            'detection2',
            'test-user',
            'This should be higher severity',
            DetectionCategory::PATH_TRAVERSAL,
            DetectionSeverity::MEDIUM,
            DetectionSeverity::HIGH,
            ['test' => true]
        );

        $this->feedbackService->submitSeverityAdjustment(
            'detection3',
            'test-user',
            'This should be higher severity',
            DetectionCategory::PATH_TRAVERSAL,
            DetectionSeverity::MEDIUM,
            DetectionSeverity::HIGH,
            ['test' => true]
        );

        // Learn from feedback
        $learningResult = $this->learningService->learnFromFeedback();

        // Verify learning result
        $this->assertTrue($learningResult['success']);

        // Verify model adjustments were applied
        $receivedAdjustments = $this->mlEngine->getReceivedAdjustments();

        // Verify PATH_TRAVERSAL adjustments
        $pathTraversalAdjustment = $this->findAdjustmentForCategory($receivedAdjustments, DetectionCategory::PATH_TRAVERSAL);
        $this->assertNotNull($pathTraversalAdjustment);

        // For severity increase, confidence should be increased (positive adjustment)
        $this->assertGreaterThan(0, $pathTraversalAdjustment->confidenceAdjustment->getValue());

        // For severity adjustments, threshold should not be adjusted
        $this->assertEquals(0, $pathTraversalAdjustment->thresholdAdjustment->getValue());
    }

    /**
     * Test that false negative feedback is properly processed
     */
    public function testFalseNegativeFeedback(): void
    {
        // Submit false negative feedback
        $this->feedbackService->submitFalseNegative(
            'detection1',
            'test-user',
            'This should have been detected',
            DetectionCategory::COMMAND_INJECTION,
            DetectionSeverity::HIGH,
            ['test' => true]
        );

        $this->feedbackService->submitFalseNegative(
            'detection2',
            'test-user',
            'This should have been detected',
            DetectionCategory::COMMAND_INJECTION,
            DetectionSeverity::HIGH,
            ['test' => true]
        );

        // Learn from feedback
        $learningResult = $this->learningService->learnFromFeedback();

        // Verify learning result
        $this->assertTrue($learningResult['success']);

        // Verify model adjustments were applied
        $receivedAdjustments = $this->mlEngine->getReceivedAdjustments();

        // Verify COMMAND_INJECTION adjustments
        $commandInjectionAdjustment = $this->findAdjustmentForCategory($receivedAdjustments, DetectionCategory::COMMAND_INJECTION);
        $this->assertNotNull($commandInjectionAdjustment);

        // For false negatives, threshold should be decreased (negative adjustment)
        $this->assertLessThan(0, $commandInjectionAdjustment->thresholdAdjustment->getValue());

        // For false negatives, confidence should be increased (positive adjustment)
        $this->assertGreaterThan(0, $commandInjectionAdjustment->confidenceAdjustment->getValue());
    }

    /**
     * Submit test feedback for various scenarios
     */
    private function submitTestFeedback(): void
    {
        // Submit false positive feedback for SQL_INJECTION
        $this->feedbackService->submitFalsePositive(
            'detection1',
            'test-user',
            'This is a legitimate query',
            DetectionCategory::SQL_INJECTION,
            DetectionSeverity::HIGH,
            ['query' => 'SELECT * FROM users WHERE id = 1']
        );

        $this->feedbackService->submitFalsePositive(
            'detection2',
            'test-user',
            'Another legitimate query',
            DetectionCategory::SQL_INJECTION,
            DetectionSeverity::HIGH,
            ['query' => 'SELECT * FROM products WHERE category_id = 2']
        );

        $this->feedbackService->submitFalsePositive(
            'detection3',
            'test-user',
            'Yet another legitimate query',
            DetectionCategory::SQL_INJECTION,
            DetectionSeverity::HIGH,
            ['query' => 'SELECT * FROM orders WHERE customer_id = 3']
        );

        // Submit correct detection feedback
        $this->feedbackService->submitCorrectDetection(
            'detection4',
            'test-user',
            'This is indeed an XSS attempt',
            DetectionCategory::XSS,
            DetectionSeverity::CRITICAL,
            ['payload' => '<script>alert("XSS")</script>']
        );

        // Submit false negative feedback
        $this->feedbackService->submitFalseNegative(
            'detection5',
            'test-user',
            'This should have been detected as XSS',
            DetectionCategory::XSS,
            DetectionSeverity::HIGH,
            ['payload' => '<img src="x" onerror="alert(\'XSS\')">']
        );
    }

    /**
     * Find an adjustment for a specific category in an array of adjustments
     *
     * @param array<string, ModelAdjustment> $adjustments Array of adjustments
     * @param DetectionCategory $category Category to find
     * @return ModelAdjustment|null The found adjustment or null
     */
    private function findAdjustmentForCategory(array $adjustments, DetectionCategory $category): ?ModelAdjustment
    {
        foreach ($adjustments as $adjustment) {
            if ($adjustment->category === $category) {
                return $adjustment;
            }
        }

        return null;
    }
}
