<?php

declare(strict_types=1);

use App\Framework\CircuitBreaker\CircuitBreakerInterface;
use App\Framework\CircuitBreaker\CircuitBreakerMetrics;
use App\Framework\CircuitBreaker\CircuitState;
use App\Framework\DDoS\Components\ServiceHealthAnalyzer;
use App\Framework\Logging\DefaultLogger;
use App\Framework\Logging\LogLevel;

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

beforeEach(function () {
    // Create a mock that mimics CircuitBreaker behavior
    $this->mockMetricsData = [];

    $this->circuitBreaker = new class ($this->mockMetricsData) implements CircuitBreakerInterface {
        private array $metricsData;

        public function __construct(array &$metricsData)
        {
            $this->metricsData = &$metricsData;
        }

        public function getMetrics(string $service): CircuitBreakerMetrics
        {
            $data = $this->metricsData[$service] ?? [
                'failure_count' => 0,
                'success_count' => 0,
                'state' => CircuitState::CLOSED,
            ];

            return new CircuitBreakerMetrics(
                state: $data['state'],
                failureCount: $data['failure_count'],
                successCount: $data['success_count'],
                halfOpenAttempts: 0,
                lastFailureTime: null,
                openedAt: null
            );
        }

        public function setTestMetrics(string $service, array $metrics): void
        {
            $this->metricsData[$service] = [
                'failure_count' => $metrics['failure_count'] ?? 0,
                'success_count' => $metrics['success_count'] ?? 0,
                'state' => $metrics['state'] ?? CircuitState::CLOSED,
            ];
        }
    };

    // Helper function to set metrics
    $this->setMetrics = function (string $service, array $metrics) {
        $this->circuitBreaker->setTestMetrics($service, $metrics);
    };

    // Create logger
    $this->logger = new DefaultLogger(LogLevel::DEBUG);

    $this->analyzer = new ServiceHealthAnalyzer($this->circuitBreaker, $this->logger);
});

describe('ServiceHealthAnalyzer', function () {

    it('analyzes service health for normal conditions', function () {
        $clientIp = '192.168.1.100';

        // Set up healthy services
        ($this->setMetrics)('web', ['failure_count' => 1, 'success_count' => 99]);
        ($this->setMetrics)('api', ['failure_count' => 0, 'success_count' => 100]);
        ($this->setMetrics)('database', ['failure_count' => 2, 'success_count' => 98]);

        $health = $this->analyzer->analyzeServiceHealth($clientIp);

        expect($health)->toHaveKeys([
            'overall_health',
            'service_scores',
            'degraded_services',
            'confidence',
            'threat_score',
        ]);

        expect($health['threat_score'])->toBeBetween(0.0, 1.0);
        expect($health['confidence'])->toBe(0.9);
        expect($health['overall_health'])->toBeLessThan(0.1); // Low threat score means healthy
    });

    it('detects degraded services', function () {
        $clientIp = '192.168.1.200';

        // Set up degraded services with high failure rates
        ($this->setMetrics)('web', ['failure_count' => 40, 'success_count' => 60]);
        ($this->setMetrics)('api', ['failure_count' => 45, 'success_count' => 55]);
        ($this->setMetrics)('database', ['failure_count' => 2, 'success_count' => 98]);

        $health = $this->analyzer->analyzeServiceHealth($clientIp);

        expect($health['overall_health'])->toBeGreaterThan(0.5); // Higher threat score
        expect($health['degraded_services'])->not()->toBeEmpty();
        expect($health['service_scores']['web'])->toBeGreaterThan(0.5);
        expect($health['service_scores']['api'])->toBeGreaterThan(0.5);
    });

    it('handles completely failed services', function () {
        $clientIp = '192.168.1.300';

        // Set up failed services with very high failure rates
        ($this->setMetrics)('web', ['failure_count' => 50, 'success_count' => 0]);
        ($this->setMetrics)('api', ['failure_count' => 40, 'success_count' => 10]);
        ($this->setMetrics)('database', ['failure_count' => 48, 'success_count' => 2]);

        $health = $this->analyzer->analyzeServiceHealth($clientIp);

        expect($health['overall_health'])->toBeGreaterThan(0.8); // Very high threat score
        expect($health['degraded_services'])->toHaveCount(3);  // All services degraded
        expect($health['service_scores']['web'])->toBe(1.0);   // Maximum degradation (100% failure rate)
    });

    it('handles missing metrics gracefully', function () {
        $clientIp = '192.168.1.400';

        // Don't set up any metrics, analyzer should use default empty metrics
        $health = $this->analyzer->analyzeServiceHealth($clientIp);

        expect($health)->toHaveKeys([
            'overall_health',
            'service_scores',
            'degraded_services',
            'confidence',
            'threat_score',
        ]);

        expect($health['overall_health'])->toBeBetween(0.0, 1.0);
        expect($health['confidence'])->toBe(0.9);
    });

    it('calculates health scores correctly from failure rates', function () {
        $clientIp = '192.168.1.500';

        // Test different failure rate scenarios with custom services
        ($this->setMetrics)('service1', ['failure_count' => 10, 'success_count' => 90]);
        ($this->setMetrics)('service2', ['failure_count' => 25, 'success_count' => 25]);

        $analyzer = new ServiceHealthAnalyzer(
            $this->circuitBreaker,
            $this->logger,
            ['service1', 'service2']
        );

        $health = $analyzer->analyzeServiceHealth($clientIp);

        // service1: 10/(10+90) = 0.1 failure rate -> 0.2 threat score
        expect($health['service_scores']['service1'])->toBe(0.2);

        // service2: 25/(25+25) = 0.5 failure rate -> 1.0 threat score (capped)
        expect($health['service_scores']['service2'])->toBe(1.0);

        // Overall: (0.2 + 1.0) / 2 = 0.6
        expect($health['overall_health'])->toBe(0.6);
    });

    it('identifies degraded services correctly', function () {
        $clientIp = '192.168.1.600';

        ($this->setMetrics)('healthy_service', ['failure_count' => 5, 'success_count' => 95]);
        ($this->setMetrics)('degraded_service', ['failure_count' => 40, 'success_count' => 60]);

        $analyzer = new ServiceHealthAnalyzer(
            $this->circuitBreaker,
            $this->logger,
            ['healthy_service', 'degraded_service']
        );

        $health = $analyzer->analyzeServiceHealth($clientIp);

        // Only services with score > 0.7 are considered degraded
        expect($health['degraded_services'])->toHaveKey('degraded_service');
        expect($health['degraded_services'])->not()->toHaveKey('healthy_service');
        expect($health['degraded_services']['degraded_service'])->toBe(0.8);
    });

    it('uses custom service list when provided', function () {
        $clientIp = '192.168.1.800';
        $customServices = ['redis', 'elasticsearch', 'rabbitmq'];

        ($this->setMetrics)('redis', ['failure_count' => 2, 'success_count' => 98]);
        ($this->setMetrics)('elasticsearch', ['failure_count' => 15, 'success_count' => 85]);
        ($this->setMetrics)('rabbitmq', ['failure_count' => 8, 'success_count' => 92]);

        $analyzer = new ServiceHealthAnalyzer(
            $this->circuitBreaker,
            $this->logger,
            $customServices
        );

        $health = $analyzer->analyzeServiceHealth($clientIp);

        expect($health['service_scores'])->toHaveKeys($customServices);
        expect($health['service_scores'])->not()->toHaveKey('web');  // Default service not included
        expect($health['service_scores'])->not()->toHaveKey('api');  // Default service not included
        expect($health['service_scores'])->not()->toHaveKey('database'); // Default service not included
    });

    it('handles circuit breaker exceptions gracefully', function () {
        $clientIp = '192.168.1.700';

        // Create a CircuitBreaker that throws exceptions
        $faultyCircuitBreaker = new class () implements CircuitBreakerInterface {
            public function getMetrics(string $service): CircuitBreakerMetrics
            {
                throw new \RuntimeException("Circuit breaker service unavailable");
            }
        };

        $analyzer = new ServiceHealthAnalyzer($faultyCircuitBreaker, $this->logger);

        $health = $analyzer->analyzeServiceHealth($clientIp);

        // Should handle exception gracefully and use neutral score (0.5)
        expect($health['service_scores']['web'])->toBe(0.5);
        expect($health['service_scores']['api'])->toBe(0.5);
        expect($health['service_scores']['database'])->toBe(0.5);
        expect($health['overall_health'])->toBe(0.5);
    });

});

describe('Health Score Calculations', function () {

    it('calculates correct health scores from different failure rates', function () {
        $clientIp = '192.168.1.100';

        $testCases = [
            // [failures, successes, expected_score]
            [0, 100, 0.0],    // 0% failure rate -> 0.0 * 2.0 = 0.0
            [5, 95, 0.1],     // 5% failure rate -> 0.05 * 2.0 = 0.1
            [10, 90, 0.2],    // 10% failure rate -> 0.1 * 2.0 = 0.2
            [25, 75, 0.5],    // 25% failure rate -> 0.25 * 2.0 = 0.5
            [35, 65, 0.7],    // 35% failure rate -> 0.35 * 2.0 = 0.7
            [25, 25, 1.0],    // 50% failure rate -> 0.5 * 2.0 = 1.0 (capped)
        ];

        foreach ($testCases as [$failures, $successes, $expectedScore]) {
            ($this->setMetrics)('test_service', [
                'failure_count' => $failures,
                'success_count' => $successes,
            ]);

            $analyzer = new ServiceHealthAnalyzer(
                $this->circuitBreaker,
                $this->logger,
                ['test_service']
            );

            $health = $analyzer->analyzeServiceHealth($clientIp);

            $actualScore = $health['service_scores']['test_service'];
            $failureRate = $failures / max(1, $failures + $successes);
            $calculatedScore = min(1.0, $failureRate * 2.0);

            expect($actualScore)->toBe(
                $calculatedScore,
                "Expected score for {$failures} failures, {$successes} successes: {$calculatedScore}, got: {$actualScore}"
            );
        }
    });

    it('handles edge cases in failure rate calculation', function () {
        $clientIp = '192.168.1.200';

        // Test case: 0 total requests (fresh service)
        $analyzer = new ServiceHealthAnalyzer(
            $this->circuitBreaker,
            $this->logger,
            ['empty_service']
        );

        $health = $analyzer->analyzeServiceHealth($clientIp);

        // Should handle division by zero gracefully - fresh service has 0 failures, 0 successes
        // max(1, 0+0) = 1, so 0/1 = 0.0 failure rate
        expect($health['service_scores']['empty_service'])->toBe(0.0);
    });

});
