<?php

declare(strict_types=1);

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\DateTime\SystemClock;
use App\Framework\DDoS\DDoSConfig;
use App\Framework\DDoS\Response\AdaptiveResponseSystem;
use App\Framework\DDoS\ValueObjects\AttackPattern;
use App\Framework\DDoS\ValueObjects\ThreatLevel;

require_once __DIR__ . '/../Helpers/TestHelpers.php';

beforeEach(function () {
    $this->clock = new SystemClock();
    $this->config = new DDoSConfig();
    $this->responseSystem = new AdaptiveResponseSystem($this->clock, $this->config);
});

describe('AdaptiveResponseSystem', function () {

    it('allows low threat requests through', function () {
        $assessment = createThreatAssessment(ThreatLevel::LOW, 0.1);
        $request = createTestRequest('192.168.1.100', 'GET', '/api/users');

        $response = $this->responseSystem->executeResponse($assessment, $request);

        expect($response->action)->toBe('allow');
        expect($response->shouldBlock)->toBeFalse();
        expect($response->httpStatusCode)->toBe(200);
    });

    it('applies rate limiting for medium threats', function () {
        $assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.5, [AttackPattern::VOLUMETRIC_ATTACK]);
        $request = createTestRequest('192.168.1.200', 'GET', '/api/data');

        $response = $this->responseSystem->executeResponse($assessment, $request);

        expect($response->action)->toBe('rate_limit');
        expect($response->shouldBlock)->toBeFalse();
        expect($response->rateLimitHeaders)->toHaveKeys(['X-RateLimit-Limit', 'X-RateLimit-Remaining']);
        expect($response->httpStatusCode)->toBe(429);
    });

    it('blocks high threat requests', function () {
        $assessment = createThreatAssessment(ThreatLevel::HIGH, 0.8, [AttackPattern::BOT_ATTACK]);
        $request = createTestRequest('192.168.1.300', 'POST', '/api/sensitive');

        $response = $this->responseSystem->executeResponse($assessment, $request);

        expect($response->action)->toBe('block');
        expect($response->shouldBlock)->toBeTrue();
        expect($response->httpStatusCode)->toBe(403);
        expect($response->blockDuration)->toBeInstanceOf(Duration::class);
    });

    it('immediately blocks critical threats', function () {
        $assessment = createThreatAssessment(ThreatLevel::CRITICAL, 0.95, [
            AttackPattern::VOLUMETRIC_ATTACK,
            AttackPattern::APPLICATION_LAYER_ATTACK,
        ]);
        $request = createTestRequest('192.168.1.400', 'POST', '/admin/delete');

        $response = $this->responseSystem->executeResponse($assessment, $request);

        expect($response->action)->toBe('block');
        expect($response->shouldBlock)->toBeTrue();
        expect($response->httpStatusCode)->toBe(403);
        expect($response->blockDuration->toMinutes())->toBeGreaterThan(60); // Long block
    });

    it('issues captcha challenges for suspicious requests', function () {
        $assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.6, [AttackPattern::BOT_ATTACK]);
        $request = createTestRequest('192.168.1.500', 'GET', '/api/search');

        $response = $this->responseSystem->executeResponse($assessment, $request);

        expect($response->action)->toBe('captcha_challenge');
        expect($response->shouldBlock)->toBeFalse();
        expect($response->challengeData)->toHaveKey('captcha_token');
        expect($response->httpStatusCode)->toBe(202);
    });

    it('requires proof of work for sustained attacks', function () {
        $assessment = createThreatAssessment(ThreatLevel::HIGH, 0.75, [AttackPattern::COORDINATED_ATTACK]);
        $request = createTestRequest('192.168.1.600', 'GET', '/api/expensive-operation');

        // Enable proof of work in config
        $config = new DDoSConfig(enableProofOfWork: true);
        $responseSystem = new AdaptiveResponseSystem($this->clock, $config);

        $response = $responseSystem->executeResponse($assessment, $request);

        expect($response->action)->toBe('proof_of_work');
        expect($response->challengeData)->toHaveKeys(['difficulty', 'challenge', 'algorithm']);
        expect($response->httpStatusCode)->toBe(202);
    });

    it('adapts response based on attack patterns', function () {
        // Volumetric attack should trigger rate limiting
        $volumetricAssessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.6, [AttackPattern::VOLUMETRIC_ATTACK]);
        $request1 = createTestRequest('192.168.1.700', 'GET', '/api/data');

        $response1 = $this->responseSystem->executeResponse($volumetricAssessment, $request1);
        expect($response1->action)->toBe('rate_limit');

        // Application layer attack should trigger blocking
        $appLayerAssessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.6, [AttackPattern::APPLICATION_LAYER_ATTACK]);
        $request2 = createTestRequest('192.168.1.701', 'POST', '/api/upload');

        $response2 = $this->responseSystem->executeResponse($appLayerAssessment, $request2);
        expect($response2->action)->toBe('block');
    });

    it('escalates responses for repeated offenses', function () {
        $clientIp = '192.168.1.800';

        // First offense - rate limit
        $assessment1 = createThreatAssessment(ThreatLevel::MEDIUM, 0.5);
        $request1 = createTestRequest($clientIp, 'GET', '/api/data1');
        $response1 = $this->responseSystem->executeResponse($assessment1, $request1);
        expect($response1->action)->toBe('rate_limit');

        // Record the offense
        $this->responseSystem->recordOffense($clientIp, $assessment1);

        // Second offense - should escalate to block
        $assessment2 = createThreatAssessment(ThreatLevel::MEDIUM, 0.5);
        $request2 = createTestRequest($clientIp, 'GET', '/api/data2');
        $response2 = $this->responseSystem->executeResponse($assessment2, $request2);
        expect($response2->action)->toBe('block');
    });

    it('applies geographic blocking when enabled', function () {
        $config = new DDoSConfig(
            enableGeographicBlocking: true,
            blockedCountries: ['CN', 'RU']
        );
        $responseSystem = new AdaptiveResponseSystem($this->clock, $config);

        $assessment = createThreatAssessment(ThreatLevel::LOW, 0.2);
        $request = createTestRequest('203.0.113.195', 'GET', '/api/test'); // Assume this IP is from CN

        // Mock geographic detection
        $responseSystem->setGeographicInfo('203.0.113.195', 'CN');

        $response = $responseSystem->executeResponse($assessment, $request);

        expect($response->action)->toBe('block');
        expect($response->blockReason)->toContain('geographic');
    });

    it('allows trusted IPs through regardless of threat level', function () {
        $config = new DDoSConfig(trustedIps: ['192.168.1.999']);
        $responseSystem = new AdaptiveResponseSystem($this->clock, $config);

        $assessment = createThreatAssessment(ThreatLevel::CRITICAL, 0.95);
        $request = createTestRequest('192.168.1.999', 'GET', '/admin/critical');

        $response = $responseSystem->executeResponse($assessment, $request);

        expect($response->action)->toBe('allow');
        expect($response->shouldBlock)->toBeFalse();
    });

    it('bypasses protection for exempt paths', function () {
        $config = new DDoSConfig(exemptPaths: ['/health', '/monitoring']);
        $responseSystem = new AdaptiveResponseSystem($this->clock, $config);

        $assessment = createThreatAssessment(ThreatLevel::HIGH, 0.8);
        $request = createTestRequest('192.168.1.100', 'GET', '/health/check');

        $response = $responseSystem->executeResponse($assessment, $request);

        expect($response->action)->toBe('allow');
        expect($response->shouldBlock)->toBeFalse();
    });

    it('provides detailed response metrics', function () {
        $assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.6);
        $request = createTestRequest('192.168.1.100', 'GET', '/api/test');

        $response = $this->responseSystem->executeResponse($assessment, $request);

        expect($response->metrics)->toHaveKeys([
            'processing_time_ms',
            'decision_confidence',
            'escalation_level',
            'historical_offenses',
        ]);

        expect($response->metrics['processing_time_ms'])->toBeLessThan(100);
        expect($response->metrics['decision_confidence'])->toBeBetween(0.0, 1.0);
    });

    it('handles circuit breaker integration', function () {
        $config = new DDoSConfig(enableCircuitBreakerIntegration: true);
        $responseSystem = new AdaptiveResponseSystem($this->clock, $config);

        // Simulate circuit breaker open state
        $responseSystem->setCircuitBreakerState('open');

        $assessment = createThreatAssessment(ThreatLevel::LOW, 0.1);
        $request = createTestRequest('192.168.1.100', 'GET', '/api/test');

        $response = $responseSystem->executeResponse($assessment, $request);

        expect($response->action)->toBe('rate_limit'); // Fallback during circuit breaker open
        expect($response->responseHeaders)->toHaveKey('X-Circuit-Breaker-State');
    });

    it('generates adaptive rate limits based on system load', function () {
        $assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.5);
        $request = createTestRequest('192.168.1.100', 'GET', '/api/data');

        // Simulate high system load
        $this->responseSystem->setSystemLoad(0.9);

        $response = $this->responseSystem->executeResponse($assessment, $request);

        expect($response->action)->toBe('rate_limit');
        expect($response->rateLimitHeaders['X-RateLimit-Limit'])->toBeLessThan(60); // Stricter limits under load
    });

    it('logs security events for blocked requests', function () {
        $assessment = createThreatAssessment(ThreatLevel::HIGH, 0.8);
        $request = createTestRequest('192.168.1.100', 'POST', '/api/sensitive');

        $response = $this->responseSystem->executeResponse($assessment, $request);

        expect($response->securityEventLogged)->toBeTrue();
        expect($response->logContext)->toHaveKeys([
            'client_ip',
            'threat_level',
            'attack_patterns',
            'response_action',
        ]);
    });

    it('provides response recommendations', function () {
        $assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.6, [AttackPattern::VOLUMETRIC_ATTACK]);
        $request = createTestRequest('192.168.1.100', 'GET', '/api/data');

        $recommendations = $this->responseSystem->getResponseRecommendations($assessment, $request);

        expect($recommendations)->toBeArray();
        expect($recommendations)->toContain('implement_rate_limiting');
        expect($recommendations)->toContain('monitor_traffic_patterns');
    });

});

describe('Response Strategy Selection', function () {

    it('selects appropriate strategy for different attack types', function () {
        $strategies = [
            [AttackPattern::VOLUMETRIC_ATTACK, 'rate_limit'],
            [AttackPattern::BOT_ATTACK, 'captcha_challenge'],
            [AttackPattern::APPLICATION_LAYER_ATTACK, 'block'],
            [AttackPattern::DISTRIBUTED_ATTACK, 'rate_limit'],
            [AttackPattern::COORDINATED_ATTACK, 'block'],
        ];

        foreach ($strategies as [$pattern, $expectedAction]) {
            $assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.6, [$pattern]);
            $request = createTestRequest('192.168.1.100', 'GET', '/api/test');

            $response = $this->responseSystem->executeResponse($assessment, $request);
            expect($response->action)->toBe($expectedAction);
        }
    });

    it('combines multiple strategies for complex attacks', function () {
        $assessment = createThreatAssessment(ThreatLevel::HIGH, 0.8, [
            AttackPattern::VOLUMETRIC_ATTACK,
            AttackPattern::BOT_ATTACK,
            AttackPattern::APPLICATION_LAYER_ATTACK,
        ]);
        $request = createTestRequest('192.168.1.100', 'POST', '/api/critical');

        $response = $this->responseSystem->executeResponse($assessment, $request);

        expect($response->action)->toBe('block'); // Most restrictive action
        expect($response->additionalMeasures)->toContain('enhanced_logging');
        expect($response->additionalMeasures)->toContain('alert_security_team');
    });

    it('adjusts strategy based on request context', function () {
        // Same threat level but different paths should get different responses
        $assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.6);

        $publicRequest = createTestRequest('192.168.1.100', 'GET', '/api/public-data');
        $adminRequest = createTestRequest('192.168.1.100', 'GET', '/admin/users');

        $publicResponse = $this->responseSystem->executeResponse($assessment, $publicRequest);
        $adminResponse = $this->responseSystem->executeResponse($assessment, $adminRequest);

        // Admin endpoint should be more strictly protected
        expect($adminResponse->action)->toBeIn(['block', 'captcha_challenge']);
        expect($publicResponse->action)->toBeIn(['allow', 'rate_limit']);
    });

});

describe('Performance', function () {

    it('completes response execution within time limit', function () {
        $assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.5);
        $request = createTestRequest('192.168.1.100', 'GET', '/api/test');

        $start = microtime(true);
        $response = $this->responseSystem->executeResponse($assessment, $request);
        $duration = microtime(true) - $start;

        expect($duration)->toBeLessThan(0.05); // Should complete within 50ms
        expect($response)->not()->toBeNull();
    });

    it('handles high request volume efficiently', function () {
        $start = microtime(true);

        // Process 100 responses
        for ($i = 1; $i <= 100; $i++) {
            $assessment = createThreatAssessment(ThreatLevel::LOW, 0.1);
            $ip = '192.168.1.' . ($i % 254 + 1);
            $request = createTestRequest($ip, 'GET', "/api/test{$i}");
            $this->responseSystem->executeResponse($assessment, $request);
        }

        $duration = microtime(true) - $start;
        $avgPerResponse = $duration / 100;

        expect($avgPerResponse)->toBeLessThan(0.01); // Average <10ms per response
    });

});

// Helper functions are now in ../Helpers/TestHelpers.php
