<?php

declare(strict_types=1);

use App\Framework\Logging\DefaultChannelLogger;
use App\Framework\Logging\HasChannel;
use App\Framework\Logging\LogChannel;
use App\Framework\Logging\Logger;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\SupportsChannels;
use App\Framework\Logging\ValueObjects\LogContext;

describe('DefaultChannelLogger', function () {
    beforeEach(function () {
        // Create a test logger that records calls
        $this->logCalls = [];
        $this->testLogger = new class($this->logCalls) implements Logger, SupportsChannels {
            public function __construct(private array &$logCalls)
            {
            }

            public function debug(string $message, ?LogContext $context = null): void
            {
                $this->logToChannel(LogChannel::FRAMEWORK, LogLevel::DEBUG, $message, $context);
            }

            public function info(string $message, ?LogContext $context = null): void
            {
                $this->logToChannel(LogChannel::FRAMEWORK, LogLevel::INFO, $message, $context);
            }

            public function notice(string $message, ?LogContext $context = null): void
            {
                $this->logToChannel(LogChannel::FRAMEWORK, LogLevel::NOTICE, $message, $context);
            }

            public function warning(string $message, ?LogContext $context = null): void
            {
                $this->logToChannel(LogChannel::FRAMEWORK, LogLevel::WARNING, $message, $context);
            }

            public function error(string $message, ?LogContext $context = null): void
            {
                $this->logToChannel(LogChannel::ERROR, LogLevel::ERROR, $message, $context);
            }

            public function critical(string $message, ?LogContext $context = null): void
            {
                $this->logToChannel(LogChannel::ERROR, LogLevel::CRITICAL, $message, $context);
            }

            public function alert(string $message, ?LogContext $context = null): void
            {
                $this->logToChannel(LogChannel::ERROR, LogLevel::ALERT, $message, $context);
            }

            public function emergency(string $message, ?LogContext $context = null): void
            {
                $this->logToChannel(LogChannel::ERROR, LogLevel::EMERGENCY, $message, $context);
            }

            public function log(LogLevel $level, string $message, ?LogContext $context = null): void
            {
                $this->logToChannel(LogChannel::FRAMEWORK, $level, $message, $context);
            }

            public function logToChannel(
                LogChannel $channel,
                LogLevel $level,
                string $message,
                ?LogContext $context = null
            ): void {
                $this->logCalls[] = [
                    'channel' => $channel,
                    'level' => $level,
                    'message' => $message,
                    'context' => $context
                ];
            }

            public function channel(LogChannel|string $channel): Logger&HasChannel
            {
                $logChannel = $channel instanceof LogChannel ? $channel : LogChannel::from($channel);
                return new DefaultChannelLogger($this, $logChannel);
            }
        };

        $this->channelLogger = new DefaultChannelLogger($this->testLogger, LogChannel::SECURITY);
    });

    describe('interface implementation', function () {
        it('implements Logger and HasChannel interfaces', function () {
            expect($this->channelLogger)->toBeInstanceOf(Logger::class);
            expect($this->channelLogger)->toBeInstanceOf(HasChannel::class);
        });

        it('has correct channel property', function () {
            expect($this->channelLogger->channel)->toBe(LogChannel::SECURITY);
        });
    });

    describe('log level delegation', function () {
        it('delegates debug to parent logger', function () {
            $this->channelLogger->debug('Test message', LogContext::withData(['key' => 'value']));

            expect(count($this->logCalls))->toBe(1);
            expect($this->logCalls[0]['channel'])->toBe(LogChannel::SECURITY);
            expect($this->logCalls[0]['level'])->toBe(LogLevel::DEBUG);
            expect($this->logCalls[0]['message'])->toBe('Test message');
        });

        it('delegates info to parent logger', function () {
            $this->channelLogger->info('Test message', LogContext::withData(['key' => 'value']));

            expect(count($this->logCalls))->toBe(1);
            expect($this->logCalls[0]['level'])->toBe(LogLevel::INFO);
        });

        it('delegates warning to parent logger', function () {
            $this->channelLogger->warning('Test message', LogContext::withData(['key' => 'value']));

            expect(count($this->logCalls))->toBe(1);
            expect($this->logCalls[0]['level'])->toBe(LogLevel::WARNING);
        });

        it('delegates error to parent logger', function () {
            $this->channelLogger->error('Test message', LogContext::withData(['key' => 'value']));

            expect(count($this->logCalls))->toBe(1);
            expect($this->logCalls[0]['level'])->toBe(LogLevel::ERROR);
        });

        it('delegates critical to parent logger', function () {
            $this->channelLogger->critical('Test message', LogContext::withData(['key' => 'value']));

            expect(count($this->logCalls))->toBe(1);
            expect($this->logCalls[0]['level'])->toBe(LogLevel::CRITICAL);
        });

        it('delegates alert to parent logger', function () {
            $this->channelLogger->alert('Test message', LogContext::withData(['key' => 'value']));

            expect(count($this->logCalls))->toBe(1);
            expect($this->logCalls[0]['level'])->toBe(LogLevel::ALERT);
        });

        it('delegates emergency to parent logger', function () {
            $this->channelLogger->emergency('Test message', LogContext::withData(['key' => 'value']));

            expect(count($this->logCalls))->toBe(1);
            expect($this->logCalls[0]['level'])->toBe(LogLevel::EMERGENCY);
        });

        it('delegates generic log to parent logger', function () {
            $this->channelLogger->log(LogLevel::WARNING, 'Test message', LogContext::withData(['key' => 'value']));

            expect(count($this->logCalls))->toBe(1);
            expect($this->logCalls[0]['level'])->toBe(LogLevel::WARNING);
            expect($this->logCalls[0]['message'])->toBe('Test message');
        });
    });

    describe('context handling', function () {
        it('supports log context objects', function () {
            $logContext = LogContext::withData(['user_id' => 123]);

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

            expect(count($this->logCalls))->toBe(1);
            expect($this->logCalls[0]['context'])->toBeInstanceOf(LogContext::class);
        });

        it('supports empty context', function () {
            $this->channelLogger->info('Test message');

            expect(count($this->logCalls))->toBe(1);
            expect($this->logCalls[0]['message'])->toBe('Test message');
        });
    });

    describe('channel independence', function () {
        it('different channels work independently', function () {
            $logCalls = [];
            $testLogger = new class($logCalls) implements Logger, SupportsChannels {
                public function __construct(private array &$logCalls)
                {
                }

                public function debug(string $message, ?LogContext $context = null): void
                {
                    $this->logToChannel(LogChannel::FRAMEWORK, LogLevel::DEBUG, $message, $context);
                }

                public function info(string $message, ?LogContext $context = null): void
                {
                    $this->logToChannel(LogChannel::FRAMEWORK, LogLevel::INFO, $message, $context);
                }

                public function notice(string $message, ?LogContext $context = null): void
                {
                    $this->logToChannel(LogChannel::FRAMEWORK, LogLevel::NOTICE, $message, $context);
                }

                public function warning(string $message, ?LogContext $context = null): void
                {
                    $this->logToChannel(LogChannel::FRAMEWORK, LogLevel::WARNING, $message, $context);
                }

                public function error(string $message, ?LogContext $context = null): void
                {
                    $this->logToChannel(LogChannel::ERROR, LogLevel::ERROR, $message, $context);
                }

                public function critical(string $message, ?LogContext $context = null): void
                {
                    $this->logToChannel(LogChannel::ERROR, LogLevel::CRITICAL, $message, $context);
                }

                public function alert(string $message, ?LogContext $context = null): void
                {
                    $this->logToChannel(LogChannel::ERROR, LogLevel::ALERT, $message, $context);
                }

                public function emergency(string $message, ?LogContext $context = null): void
                {
                    $this->logToChannel(LogChannel::ERROR, LogLevel::EMERGENCY, $message, $context);
                }

                public function log(LogLevel $level, string $message, ?LogContext $context = null): void
                {
                    $this->logToChannel(LogChannel::FRAMEWORK, $level, $message, $context);
                }

                public function logToChannel(
                    LogChannel $channel,
                    LogLevel $level,
                    string $message,
                    ?LogContext $context = null
                ): void {
                    $this->logCalls[] = ['channel' => $channel];
                }

                public function channel(LogChannel|string $channel): Logger&HasChannel
                {
                    $logChannel = $channel instanceof LogChannel ? $channel : LogChannel::from($channel);
                    return new DefaultChannelLogger($this, $logChannel);
                }
            };

            $cacheLogger = new DefaultChannelLogger($testLogger, LogChannel::CACHE);
            $dbLogger = new DefaultChannelLogger($testLogger, LogChannel::DATABASE);
            $securityLogger = new DefaultChannelLogger($testLogger, LogChannel::SECURITY);

            expect($cacheLogger->channel)->toBe(LogChannel::CACHE);
            expect($dbLogger->channel)->toBe(LogChannel::DATABASE);
            expect($securityLogger->channel)->toBe(LogChannel::SECURITY);
        });
    });
});
