<?php

declare(strict_types=1);

namespace Tests\Framework\Logging;

use App\Framework\Logging\DefaultLogger;
use App\Framework\Logging\LogHandler;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\ProcessorManager;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Logging\ValueObjects\RequestContext;
use App\Framework\Logging\ValueObjects\UserContext;
use App\Framework\Random\SecureRandomGenerator;
use App\Framework\Tracing\TraceContext;

describe('DefaultLogger', function () {
    beforeEach(function () {
        // Clear any existing trace context
        TraceContext::clear();
    });

    afterEach(function () {
        TraceContext::clear();
    });

    it('can log with array context (legacy)', function () {
        $handler = new TestLogHandler();
        $logger = new DefaultLogger(
            minLevel: LogLevel::DEBUG,
            handlers: [$handler]
        );

        $context = ['user_id' => 123, 'action' => 'login'];
        $logger->info('User logged in', $context);

        expect($handler->records)->toHaveCount(1);
        $record = $handler->records[0];

        expect($record->getMessage())->toBe('User logged in');
        expect($record->getContext())->toBe($context);
        expect($record->getLevel())->toBe(LogLevel::INFO);
    });

    it('can log with LogContext', function () {
        $handler = new TestLogHandler();
        $logger = new DefaultLogger(
            minLevel: LogLevel::DEBUG,
            handlers: [$handler]
        );

        $logContext = LogContext::withData(['user_id' => 123, 'action' => 'login'])
            ->addTags('authentication', 'user-action')
            ->addMetadata('source', 'test');

        $logger->info('User logged in', $logContext);

        expect($handler->records)->toHaveCount(1);
        $record = $handler->records[0];

        expect($record->getMessage())->toBe('User logged in');
        expect($record->getLevel())->toBe(LogLevel::INFO);

        // Context should be converted to array
        $context = $record->getContext();
        expect($context)->toHaveKey('user_id', 123);
        expect($context)->toHaveKey('action', 'login');
        expect($context)->toHaveKey('_tags', ['authentication', 'user-action']);
        expect($context)->toHaveKey('source', 'test');

        // Structured data should be in extras
        expect($record->getExtra('structured_tags'))->toBe(['authentication', 'user-action']);
    });

    it('converts LogContext with trace to array context', function () {
        $handler = new TestLogHandler();
        $logger = new DefaultLogger(
            minLevel: LogLevel::DEBUG,
            handlers: [$handler]
        );

        $trace = TraceContext::start(new SecureRandomGenerator());
        $span = $trace->startSpan('test-span');

        $logContext = LogContext::withData(['key' => 'value'])
            ->withTrace($trace);

        $logger->info('Test message', $logContext);

        $record = $handler->records[0];
        $context = $record->getContext();

        expect($context)->toHaveKey('_trace_id', $trace->getTraceId());
        expect($context)->toHaveKey('_span_id', $span->spanId);

        // Trace should also be in extras
        $traceExtra = $record->getExtra('trace_context');
        expect($traceExtra)->toHaveKey('trace_id', $trace->getTraceId());
        expect($traceExtra)->toHaveKey('active_span');
    });

    it('converts LogContext with user to array context', function () {
        $handler = new TestLogHandler();
        $logger = new DefaultLogger(
            minLevel: LogLevel::DEBUG,
            handlers: [$handler]
        );

        $userContext = UserContext::authenticated('123', 'john');
        $logContext = LogContext::withData(['key' => 'value'])
            ->withUser($userContext);

        $logger->info('Test message', $logContext);

        $record = $handler->records[0];
        $context = $record->getContext();

        expect($context)->toHaveKey('_user_id', '123');

        // User should also be in extras
        $userExtra = $record->getExtra('user_context');
        expect($userExtra)->toBeArray();
        expect($userExtra)->toHaveKey('user_id', '123');
    });

    it('converts LogContext with request to array context', function () {
        $handler = new TestLogHandler();
        $logger = new DefaultLogger(
            minLevel: LogLevel::DEBUG,
            handlers: [$handler]
        );

        $requestContext = RequestContext::empty();
        $logContext = LogContext::withData(['key' => 'value'])
            ->withRequest($requestContext);

        $logger->info('Test message', $logContext);

        $record = $handler->records[0];

        // Request should be in extras
        $requestExtra = $record->getExtra('request_context');
        expect($requestExtra)->toBeArray();
    });

    it('logs at different levels with LogContext', function () {
        $handler = new TestLogHandler();
        $logger = new DefaultLogger(
            minLevel: LogLevel::DEBUG,
            handlers: [$handler]
        );

        $context = LogContext::withData(['test' => 'value']);

        $logger->debug('Debug message', $context);
        $logger->info('Info message', $context);
        $logger->notice('Notice message', $context);
        $logger->warning('Warning message', $context);
        $logger->error('Error message', $context);
        $logger->critical('Critical message', $context);
        $logger->alert('Alert message', $context);
        $logger->emergency('Emergency message', $context);

        expect($handler->records)->toHaveCount(8);

        $levels = array_map(fn ($record) => $record->getLevel(), $handler->records);
        expect($levels)->toBe([
            LogLevel::DEBUG,
            LogLevel::INFO,
            LogLevel::NOTICE,
            LogLevel::WARNING,
            LogLevel::ERROR,
            LogLevel::CRITICAL,
            LogLevel::ALERT,
            LogLevel::EMERGENCY,
        ]);
    });

    it('respects minimum log level', function () {
        $handler = new TestLogHandler();
        $logger = new DefaultLogger(
            minLevel: LogLevel::WARNING,
            handlers: [$handler]
        );

        $context = LogContext::withData(['test' => 'value']);

        $logger->debug('Debug message', $context);
        $logger->info('Info message', $context);
        $logger->warning('Warning message', $context);
        $logger->error('Error message', $context);

        expect($handler->records)->toHaveCount(2);
        expect($handler->records[0]->getLevel())->toBe(LogLevel::WARNING);
        expect($handler->records[1]->getLevel())->toBe(LogLevel::ERROR);
    });

    it('calls processors on enriched record', function () {
        $processor = new TestLogProcessor();
        $processorManager = new ProcessorManager($processor);
        $handler = new TestLogHandler();

        $logger = new DefaultLogger(
            minLevel: LogLevel::DEBUG,
            handlers: [$handler],
            processorManager: $processorManager
        );

        $context = LogContext::withData(['test' => 'value']);
        $logger->info('Test message', $context);

        expect($processor->processedRecords)->toHaveCount(1);
        expect($handler->records)->toHaveCount(1);

        // Processor should have seen the enriched record
        $processedRecord = $processor->processedRecords[0];
        expect($processedRecord->getExtra('structured_tags'))->toBeNull(); // No tags in this test
    });

    it('handles empty LogContext', function () {
        $handler = new TestLogHandler();
        $logger = new DefaultLogger(
            minLevel: LogLevel::DEBUG,
            handlers: [$handler]
        );

        $logger->info('Test message', LogContext::empty());

        expect($handler->records)->toHaveCount(1);
        $record = $handler->records[0];

        expect($record->getContext())->toBe([]);
        expect($record->getExtras())->toBe([]);
    });

    it('can get configuration', function () {
        $handler = new TestLogHandler();
        $processor = new TestLogProcessor();
        $processorManager = new ProcessorManager($processor);

        $logger = new DefaultLogger(
            minLevel: LogLevel::INFO,
            handlers: [$handler],
            processorManager: $processorManager
        );

        $config = $logger->getConfiguration();

        expect($config)->toHaveKey('minLevel', 200);
        expect($config)->toHaveKey('handlers');
        expect($config)->toHaveKey('processors');
        expect($config['handlers'])->toBe([TestLogHandler::class]);
    });
});

// Test helpers
class TestLogHandler implements LogHandler
{
    public array $records = [];

    public function isHandling(LogRecord $record): bool
    {
        return true;
    }

    public function handle(LogRecord $record): void
    {
        $this->records[] = $record;
    }
}

class TestLogProcessor implements \App\Framework\Logging\LogProcessor
{
    public array $processedRecords = [];

    public function processRecord(LogRecord $record): LogRecord
    {
        $this->processedRecords[] = $record;

        return $record->addExtra('processed_by', 'TestLogProcessor');
    }

    public function getPriority(): int
    {
        return 100;
    }

    public function getName(): string
    {
        return 'test-processor';
    }
}
