<?php

declare(strict_types=1);

namespace Tests\Unit\Framework\Logging;

use App\Framework\Logging\DefaultLogger;
use App\Framework\Logging\LogChannel;
use App\Framework\Logging\LogHandler;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\ValueObjects\LogContext;

beforeEach(function () {
    // Reset any static state
});

it('can create a logger with default settings', function () {
    $logger = new DefaultLogger();

    expect($logger)->toBeInstanceOf(DefaultLogger::class);
    expect($logger->getConfiguration()['minLevel'])->toBe(LogLevel::DEBUG->value);
});

it('can create a logger with custom minimum level', function () {
    $logger = new DefaultLogger(minLevel: LogLevel::WARNING);

    expect($logger->getConfiguration()['minLevel'])->toBe(LogLevel::WARNING->value);
});

it('respects minimum log level and filters lower priority messages', function () {
    $mockHandler = new class () implements LogHandler {
        public array $handledRecords = [];

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

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

    $logger = new DefaultLogger(
        minLevel: LogLevel::WARNING,
        handlers: [$mockHandler]
    );

    // These should be ignored (lower than WARNING)
    $logger->debug('Debug message');
    $logger->info('Info message');
    $logger->notice('Notice message');

    // These should be handled (WARNING or higher)
    $logger->warning('Warning message');
    $logger->error('Error message');
    $logger->critical('Critical message');

    expect($mockHandler->handledRecords)->toHaveCount(3);
    expect($mockHandler->handledRecords[0]->getMessage())->toBe('Warning message');
    expect($mockHandler->handledRecords[1]->getMessage())->toBe('Error message');
    expect($mockHandler->handledRecords[2]->getMessage())->toBe('Critical message');
});

it('can log messages with all severity levels', function () {
    $recordedLevels = [];

    $mockHandler = new class ($recordedLevels) implements LogHandler {
        public function __construct(private array &$recordedLevels)
        {
        }

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

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

    $logger = new DefaultLogger(handlers: [$mockHandler]);

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

    expect($recordedLevels)->toBe([
        LogLevel::DEBUG->value,
        LogLevel::INFO->value,
        LogLevel::NOTICE->value,
        LogLevel::WARNING->value,
        LogLevel::ERROR->value,
        LogLevel::CRITICAL->value,
        LogLevel::ALERT->value,
        LogLevel::EMERGENCY->value,
    ]);
});

it('can log with LogContext', function () {
    $capturedContext = null;

    $mockHandler = new class ($capturedContext) implements LogHandler {
        public function __construct(private ?array &$capturedContext)
        {
        }

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

        public function handle(LogRecord $record): void
        {
            $this->capturedContext = $record->getContext();
        }
    };

    $logger = new DefaultLogger(handlers: [$mockHandler]);

    $context = LogContext::withData(['user_id' => 123, 'action' => 'login']);
    $logger->info('User action', $context);

    expect($capturedContext)->toMatchArray(['user_id' => 123, 'action' => 'login']);
});

it('can log with null context', function () {
    $capturedContext = null;

    $mockHandler = new class ($capturedContext) implements LogHandler {
        public function __construct(private ?array &$capturedContext)
        {
        }

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

        public function handle(LogRecord $record): void
        {
            $this->capturedContext = $record->getContext();
        }
    };

    $logger = new DefaultLogger(handlers: [$mockHandler]);

    // Log without context
    $logger->info('Simple message');

    expect($capturedContext)->toBe([]);
});

it('can be created without ProcessorManager', function () {
    $processedRecord = null;

    $mockHandler = new class ($processedRecord) implements LogHandler {
        public function __construct(private ?LogRecord &$processedRecord)
        {
        }

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

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

    $logger = new DefaultLogger(
        handlers: [$mockHandler]
    );

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

    expect($processedRecord)->toBeInstanceOf(LogRecord::class);
    expect($processedRecord->getMessage())->toBe('Test message');
});

it('logs with LogContext including tags', function () {
    $capturedContext = null;

    $mockHandler = new class ($capturedContext) implements LogHandler {
        public function __construct(private ?array &$capturedContext)
        {
        }

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

        public function handle(LogRecord $record): void
        {
            $this->capturedContext = $record->getContext();
        }
    };

    $logger = new DefaultLogger(
        handlers: [$mockHandler]
    );

    $context = LogContext::withData(['request_id' => 'req-123'])
        ->addTags('api', 'v2');
    $logger->info('API request', $context);

    expect($capturedContext)->toMatchArray([
        'request_id' => 'req-123',
        '_tags' => ['api', 'v2'],
    ]);
});

it('provides channel loggers for different log channels', function () {
    $logger = new DefaultLogger();

    // Test that channel() method returns Logger & HasChannel instances
    $securityLogger = $logger->channel(LogChannel::SECURITY);
    expect($securityLogger->channel)->toBe(LogChannel::SECURITY);

    $cacheLogger = $logger->channel(LogChannel::CACHE);
    expect($cacheLogger->channel)->toBe(LogChannel::CACHE);

    $databaseLogger = $logger->channel(LogChannel::DATABASE);
    expect($databaseLogger->channel)->toBe(LogChannel::DATABASE);

    $frameworkLogger = $logger->channel(LogChannel::FRAMEWORK);
    expect($frameworkLogger->channel)->toBe(LogChannel::FRAMEWORK);

    $errorLogger = $logger->channel(LogChannel::ERROR);
    expect($errorLogger->channel)->toBe(LogChannel::ERROR);
});

it('can get channel loggers using string names', function () {
    $logger = new DefaultLogger();

    // Test that channel() also accepts string channel names
    $securityLogger = $logger->channel('security');
    expect($securityLogger->channel)->toBe(LogChannel::SECURITY);

    // Test that same channel returns same instance (singleton per channel)
    $securityLogger2 = $logger->channel(LogChannel::SECURITY);
    expect($securityLogger2)->toBe($securityLogger);
});

it('can log to specific channels using channel loggers', function () {
    $capturedChannel = null;
    $capturedMessage = null;

    $mockHandler = new class ($capturedChannel, $capturedMessage) implements LogHandler {
        public function __construct(
            private ?string &$capturedChannel,
            private ?string &$capturedMessage
        ) {
        }

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

        public function handle(LogRecord $record): void
        {
            $this->capturedChannel = $record->getChannel();
            $this->capturedMessage = $record->getMessage();
        }
    };

    $logger = new DefaultLogger(handlers: [$mockHandler]);

    // Log to security channel with LogContext
    $context = LogContext::withData(['ip' => '192.168.1.1'])->addTags('security');
    $logger->channel(LogChannel::SECURITY)->warning('Unauthorized access attempt', $context);

    expect($capturedChannel)->toBe(LogChannel::SECURITY->value);
    expect($capturedMessage)->toBe('Unauthorized access attempt');

    // Log to database channel without context
    $logger->channel(LogChannel::DATABASE)->error('Connection failed');

    expect($capturedChannel)->toBe(LogChannel::DATABASE->value);
    expect($capturedMessage)->toBe('Connection failed');
});

it('calls multiple handlers when configured', function () {
    $handler1Called = false;
    $handler2Called = false;
    $handler3Called = false;

    $handler1 = new class ($handler1Called) implements LogHandler {
        public function __construct(private bool &$called)
        {
        }

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

        public function handle(LogRecord $record): void
        {
            $this->called = true;
        }
    };

    $handler2 = new class ($handler2Called) implements LogHandler {
        public function __construct(private bool &$called)
        {
        }

        public function isHandling(LogRecord $record): bool
        {
            return $record->getLevel()->value >= LogLevel::WARNING->value;
        }

        public function handle(LogRecord $record): void
        {
            $this->called = true;
        }
    };

    $handler3 = new class ($handler3Called) implements LogHandler {
        public function __construct(private bool &$called)
        {
        }

        public function isHandling(LogRecord $record): bool
        {
            return false; // Never handles
        }

        public function handle(LogRecord $record): void
        {
            $this->called = true;
        }
    };

    $logger = new DefaultLogger(handlers: [$handler1, $handler2, $handler3]);

    $logger->error('Test error');

    expect($handler1Called)->toBeTrue();
    expect($handler2Called)->toBeTrue();
    expect($handler3Called)->toBeFalse();
});

it('creates log records with correct timestamp', function () {
    $capturedRecord = null;

    $mockHandler = new class ($capturedRecord) implements LogHandler {
        public function __construct(private ?LogRecord &$capturedRecord)
        {
        }

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

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

    $logger = new DefaultLogger(handlers: [$mockHandler]);

    $beforeLog = new \DateTimeImmutable('now', new \DateTimeZone('Europe/Berlin'));
    $logger->info('Test timestamp');
    $afterLog = new \DateTimeImmutable('now', new \DateTimeZone('Europe/Berlin'));

    expect($capturedRecord)->toBeInstanceOf(LogRecord::class);
    expect($capturedRecord->getTimestamp())->toBeInstanceOf(\DateTimeImmutable::class);

    $timestamp = $capturedRecord->getTimestamp();
    expect($timestamp >= $beforeLog)->toBeTrue();
    expect($timestamp <= $afterLog)->toBeTrue();
    expect($timestamp->getTimezone()->getName())->toBe('Europe/Berlin');
});

it('returns correct configuration information', function () {
    $handler1 = new class () implements LogHandler {
        public function isHandling(LogRecord $record): bool
        {
            return true;
        }

        public function handle(LogRecord $record): void
        {
        }
    };

    $handler2 = new class () implements LogHandler {
        public function isHandling(LogRecord $record): bool
        {
            return true;
        }

        public function handle(LogRecord $record): void
        {
        }
    };

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

    $config = $logger->getConfiguration();

    expect($config['minLevel'])->toBe(LogLevel::INFO->value);
    expect($config['handlers'])->toHaveCount(2);
    expect($config['processors'])->toBeArray();
});
