<?php

declare(strict_types=1);

namespace Tests\Unit\Framework\Logging\Handlers;

use App\Framework\Logging\Handlers\ConsoleHandler;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\ValueObjects\LogContext;
use DateTimeImmutable;
use DateTimeZone;

beforeEach(function () {
    $this->timestamp = new DateTimeImmutable('2024-01-15 10:30:45', new DateTimeZone('Europe/Berlin'));
    $this->context = LogContext::withData(['test' => 'data']);
});

it('only handles records in CLI mode', function () {
    // ConsoleHandler should only work in CLI mode
    $cliCheck = PHP_SAPI === 'cli';
    expect($cliCheck)->toBe(true);

    // Create handler with debugOnly = false to avoid APP_DEBUG dependency
    $handler = new ConsoleHandler(debugOnly: false);

    $record = new LogRecord(
        message: 'Test message',
        context: $this->context,
        level: LogLevel::INFO,
        timestamp: $this->timestamp
    );

    // In CLI mode, should handle the record
    $result = $handler->isHandling($record);
    expect($result)->toBe(true);
});

it('respects minimum level configuration', function () {
    $handler = new ConsoleHandler(minLevel: LogLevel::WARNING, debugOnly: false);

    $debugRecord = new LogRecord(
        message: 'Debug',
        context: $this->context,
        level: LogLevel::DEBUG,
        timestamp: $this->timestamp
    );

    $infoRecord = new LogRecord(
        message: 'Info',
        context: $this->context,
        level: LogLevel::INFO,
        timestamp: $this->timestamp
    );

    $warningRecord = new LogRecord(
        message: 'Warning',
        context: $this->context,
        level: LogLevel::WARNING,
        timestamp: $this->timestamp
    );

    $errorRecord = new LogRecord(
        message: 'Error',
        context: $this->context,
        level: LogLevel::ERROR,
        timestamp: $this->timestamp
    );

    $debugResult = $handler->isHandling($debugRecord);
    expect($debugResult)->toBe(false);

    $infoResult = $handler->isHandling($infoRecord);
    expect($infoResult)->toBe(false);

    $warningResult = $handler->isHandling($warningRecord);
    expect($warningResult)->toBe(true);

    $errorResult = $handler->isHandling($errorRecord);
    expect($errorResult)->toBe(true);
});

it('respects debug only mode when APP_DEBUG is not set', function () {
    // Save original value
    $originalDebug = getenv('APP_DEBUG');

    // Test with APP_DEBUG = false
    putenv('APP_DEBUG=false');
    $handler = new ConsoleHandler(debugOnly: true);

    $record = new LogRecord(
        message: 'Test',
        context: $this->context,
        level: LogLevel::INFO,
        timestamp: $this->timestamp
    );

    $result1 = $handler->isHandling($record);
    expect($result1)->toBe(false);

    // Test with APP_DEBUG = true
    putenv('APP_DEBUG=true');
    $result2 = $handler->isHandling($record);
    expect($result2)->toBe(true);

    // Test with debugOnly = false (should always handle)
    putenv('APP_DEBUG=false');
    $handler = new ConsoleHandler(debugOnly: false);
    $result3 = $handler->isHandling($record);
    expect($result3)->toBe(true);

    // Restore original value
    if ($originalDebug !== false) {
        putenv("APP_DEBUG={$originalDebug}");
    } else {
        putenv('APP_DEBUG');
    }
});

it('can change minimum level after creation', function () {
    $handler = new ConsoleHandler(minLevel: LogLevel::DEBUG, debugOnly: false);

    $infoRecord = new LogRecord(
        message: 'Info',
        context: $this->context,
        level: LogLevel::INFO,
        timestamp: $this->timestamp
    );

    $result1 = $handler->isHandling($infoRecord);
    expect($result1)->toBe(true);

    $handler->setMinLevel(LogLevel::WARNING);

    $result2 = $handler->isHandling($infoRecord);
    expect($result2)->toBe(false);
});

it('can change output format', function () {
    $handler = new ConsoleHandler(debugOnly: false);

    $originalFormat = '{color}[{level_name}]{reset} {timestamp} {request_id}{message}{structured}';
    $newFormat = '{level_name}: {message}';

    $handler->setOutputFormat($newFormat);

    // Note: We can't easily test the actual output without mocking file_put_contents or echo,
    // but we can verify the method returns the handler for fluent interface
    $result = $handler->setOutputFormat($newFormat);
    expect($result)->toBe($handler);
});

it('handles output correctly using stdout and stderr', function () {
    $handler = new ConsoleHandler(stderrLevel: LogLevel::WARNING, debugOnly: false);

    // Test that lower levels would go to stdout (DEBUG, INFO, NOTICE)
    $infoRecord = new LogRecord(
        message: 'Info message',
        context: $this->context,
        level: LogLevel::INFO,
        timestamp: $this->timestamp
    );

    // Test that higher levels would go to stderr (WARNING and above)
    $errorRecord = new LogRecord(
        message: 'Error message',
        context: $this->context,
        level: LogLevel::ERROR,
        timestamp: $this->timestamp
    );

    // We can verify the handler processes these records
    $result1 = $handler->isHandling($infoRecord);
    expect($result1)->toBe(true);

    $result2 = $handler->isHandling($errorRecord);
    expect($result2)->toBe(true);

    // Capture output
    ob_start();
    $handler->handle($infoRecord);
    $stdoutOutput = ob_get_clean();

    // For stderr, we would need to redirect stderr to test it properly
    // This is complex in PHPUnit/Pest, so we just verify it handles the record
    ob_start();
    $stderrBefore = ob_get_clean();
    $handler->handle($errorRecord);

    // The handler should have processed both records
    $result3 = $handler->isHandling($infoRecord);
    expect($result3)->toBe(true);

    $result4 = $handler->isHandling($errorRecord);
    expect($result4)->toBe(true);
});

it('formats records with extra data correctly', function () {
    $handler = new ConsoleHandler(debugOnly: false);

    $record = new LogRecord(
        message: 'Test with extras',
        context: $this->context,
        level: LogLevel::INFO,
        timestamp: $this->timestamp
    );

    // Add various types of extra data
    $record = $record->withExtra('request_id', 'req-123')
        ->withExtra('structured_tags', ['important', 'audit'])
        ->withExtra('trace_context', [
            'trace_id' => 'trace-abc-def-123',
            'active_span' => ['spanId' => 'span-456-789'],
        ])
        ->withExtra('user_context', [
            'user_id' => 'user-999',
            'is_authenticated' => true,
        ])
        ->withExtra('request_context', [
            'request_method' => 'POST',
            'request_uri' => '/api/users/create',
        ]);

    // The handler should process this record
    $result = $handler->isHandling($record);
    expect($result)->toBe(true);

    // Capture the output
    ob_start();
    $handler->handle($record);
    $output = ob_get_clean();

    // The output should contain the message
    expect($output)->toContain('Test with extras');
    // It should contain the request_id
    expect($output)->toContain('req-123');
});

it('handles records with channel information', function () {
    $handler = new ConsoleHandler(
        outputFormat: '{channel}{level_name}: {message}',
        debugOnly: false
    );

    $record = new LogRecord(
        message: 'Database connection established',
        context: $this->context,
        level: LogLevel::INFO,
        timestamp: $this->timestamp,
        channel: 'database'
    );

    $result = $handler->isHandling($record);
    expect($result)->toBe(true);

    // Capture output
    ob_start();
    $handler->handle($record);
    $output = ob_get_clean();

    // The output should contain the channel
    expect($output)->toContain('[database]');
    expect($output)->toContain('Database connection established');
});

it('applies correct colors for stdout log levels', function () {
    $handler = new ConsoleHandler(debugOnly: false);

    // Only test stdout levels (DEBUG, INFO, NOTICE)
    // WARNING and above go to stderr and cannot be captured with ob_start()
    $levels = [
        LogLevel::DEBUG,
        LogLevel::INFO,
        LogLevel::NOTICE,
    ];

    foreach ($levels as $level) {
        $record = new LogRecord(
            message: "{$level->getName()} message",
            context: $this->context,
            level: $level,
            timestamp: $this->timestamp
        );

        ob_start();
        $handler->handle($record);
        $output = ob_get_clean();

        // Each level should have its color code in the output
        $expectedColor = $level->getConsoleColor()->value;
        expect($output)->toContain($expectedColor);
        expect($output)->toContain("{$level->getName()} message");
    }
});
