<?php

declare(strict_types=1);

namespace Tests\Unit\Framework\Logging;

use App\Framework\CircuitBreaker\CircuitBreaker;
use App\Framework\CircuitBreaker\CircuitBreakerConfig;
use App\Framework\Logging\Handlers\ResilientLogHandler;
use App\Framework\Logging\LogHandler;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\ValueObjects\LogContext;
use PHPUnit\Framework\TestCase;

final class ResilientLogHandlerTest extends TestCase
{
    public function test_uses_primary_handler_when_healthy(): void
    {
        $primary = $this->createMock(LogHandler::class);
        $fallback = $this->createMock(LogHandler::class);

        $record = $this->createLogRecord();

        $primary->expects($this->once())
            ->method('handle')
            ->with($record);

        $fallback->expects($this->never())
            ->method('handle');

        $handler = new ResilientLogHandler($primary, $fallback);
        $handler->handle($record);
    }

    public function test_uses_fallback_when_primary_fails(): void
    {
        $primary = $this->createMock(LogHandler::class);
        $fallback = $this->createMock(LogHandler::class);

        $record = $this->createLogRecord();

        $primary->method('handle')
            ->willThrowException(new \RuntimeException('Primary failed'));

        $fallback->expects($this->atLeastOnce())
            ->method('handle');

        $handler = new ResilientLogHandler($primary, $fallback);
        $handler->handle($record);

        // Should not throw exception
        $this->assertTrue(true);
    }

    public function test_never_throws_exception(): void
    {
        $primary = $this->createMock(LogHandler::class);
        $fallback = $this->createMock(LogHandler::class);

        $primary->method('handle')
            ->willThrowException(new \RuntimeException('Primary failed'));

        $fallback->method('handle')
            ->willThrowException(new \RuntimeException('Fallback failed'));

        $handler = new ResilientLogHandler($primary, $fallback);

        // Should never throw - uses error_log as last resort
        $handler->handle($this->createLogRecord());

        $this->assertTrue(true);
    }

    public function test_circuit_breaker_opens_after_failures(): void
    {
        $primary = $this->createMock(LogHandler::class);
        $fallback = $this->createMock(LogHandler::class);

        $circuitBreaker = new CircuitBreaker(
            new CircuitBreakerConfig(
                name: 'test',
                failureThreshold: 3,
                successThreshold: 1,
                timeout: 60.0
            )
        );

        $primary->method('handle')
            ->willThrowException(new \RuntimeException('Failed'));

        $handler = new ResilientLogHandler($primary, $fallback, $circuitBreaker);

        // Trigger failures to open circuit
        for ($i = 0; $i < 5; $i++) {
            $handler->handle($this->createLogRecord());
        }

        $this->assertTrue($circuitBreaker->isOpen());
        $this->assertFalse($handler->isHealthy());
    }

    private function createLogRecord(string $message = 'test'): LogRecord
    {
        return new LogRecord(
            level: LogLevel::INFO,
            message: $message,
            channel: 'test',
            context: LogContext::empty(),
            timestamp: new \DateTimeImmutable()
        );
    }
}
