<?php

declare(strict_types=1);

use App\Domain\Common\ValueObject\Email;
use App\Framework\Logging\ChannelLogger;
use App\Framework\Logging\LogChannel;
use App\Framework\Logging\Logger;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Mail\Commands\SendEmailCommand;
use App\Framework\Mail\Commands\SendEmailCommandHandler;
use App\Framework\Mail\EmailList;
use App\Framework\Mail\Exceptions\SmtpException;
use App\Framework\Mail\Message;
use App\Framework\Mail\Testing\MockTransport;
use App\Framework\Mail\TransportResult;

describe('SendEmailCommandHandler', function () {
    beforeEach(function () {
        $this->transport = new MockTransport();
        $this->logger = new MailTestLogger();
        $this->handler = new SendEmailCommandHandler($this->transport, $this->logger);

        $this->message = new Message(
            from: new Email('sender@example.com'),
            subject: 'Test Subject',
            body: 'Test body',
            to: new EmailList(new Email('recipient@example.com'))
        );

        $this->command = new SendEmailCommand($this->message);
    });

    it('handles successful email sending', function () {
        $this->handler->handle($this->command);

        expect($this->transport->getSentMessageCount())->toBe(1);
        expect($this->transport->getLastSentMessage()['message'])->toBe($this->message);
        expect($this->logger->hasInfo('Sending email via queue'))->toBeTrue();
        expect($this->logger->hasInfo('Email sent successfully'))->toBeTrue();
    });

    it('logs transport failure and throws SmtpException', function () {
        $this->transport->setShouldFail(true, 'SMTP connection failed');

        expect(fn () => $this->handler->handle($this->command))
            ->toThrow(SmtpException::class, 'Failed to send email: SMTP connection failed');

        expect($this->logger->hasInfo('Sending email via queue'))->toBeTrue();
        expect($this->logger->hasError('Email sending failed'))->toBeTrue();
    });

    it('logs and wraps unexpected exceptions', function () {
        // Create a transport that throws unexpected exception
        $failingTransport = new FailingTransport('Unexpected error');

        $handler = new SendEmailCommandHandler($failingTransport, $this->logger);

        expect(fn () => $handler->handle($this->command))
            ->toThrow(SmtpException::class, 'Unexpected error while sending email: Unexpected error');

        expect($this->logger->hasInfo('Sending email via queue'))->toBeTrue();
        expect($this->logger->hasError('Unexpected exception during email sending'))->toBeTrue();
    });

    it('re-throws SmtpException for retry logic', function () {
        // Create a transport that throws SmtpException
        $failingTransport = new SmtpFailingTransport();

        $handler = new SendEmailCommandHandler($failingTransport, $this->logger);

        expect(fn () => $handler->handle($this->command))
            ->toThrow(SmtpException::class, 'Failed to connect to SMTP server localhost:587: Connection refused');

        expect($this->logger->hasInfo('Sending email via queue'))->toBeTrue();
        expect($this->logger->hasError('SMTP exception during email sending'))->toBeTrue();
    });

    it('includes transport name in logs', function () {
        $this->handler->handle($this->command);

        $logs = $this->logger->getLogs();
        expect($logs[0]['context']['transport'])->toBe('Mock Transport');
        expect($logs[1]['context']['transport'])->toBe('Mock Transport');
    });

    it('includes message details in logs', function () {
        $this->handler->handle($this->command);

        $logs = $this->logger->getLogs();
        expect($logs[0]['context']['to'])->toBe('recipient@example.com');
        expect($logs[0]['context']['subject'])->toBe('Test Subject');
        expect($logs[1]['context']['to'])->toBe('recipient@example.com');
        expect($logs[1]['context']['subject'])->toBe('Test Subject');
        expect($logs[1]['context'])->toHaveKey('message_id');
    });

    it('includes error details in failure logs', function () {
        $this->transport->setShouldFail(true, 'Custom error message');

        expect(fn () => $this->handler->handle($this->command))
            ->toThrow(SmtpException::class);

        $logs = $this->logger->getLogs();
        $errorLog = $logs[1]; // Second log should be the error
        expect($errorLog['context']['error'])->toBe('Custom error message');
        expect($errorLog['context']['to'])->toBe('recipient@example.com');
        expect($errorLog['context']['subject'])->toBe('Test Subject');
        expect($errorLog['context'])->toHaveKey('metadata');
    });
});

// Test stub for mail handler
class MailTestLogger implements Logger
{
    private array $logs = [];

    public function emergency(string $message, ?LogContext $context = null): void
    {
        $this->logs[] = ['level' => 'emergency', 'message' => $message, 'context' => $context];
    }

    public function alert(string $message, ?LogContext $context = null): void
    {
        $this->logs[] = ['level' => 'alert', 'message' => $message, 'context' => $context];
    }

    public function critical(string $message, ?LogContext $context = null): void
    {
        $this->logs[] = ['level' => 'critical', 'message' => $message, 'context' => $context];
    }

    public function error(string $message, ?LogContext $context = null): void
    {
        $this->logs[] = ['level' => 'error', 'message' => $message, 'context' => $context];
    }

    public function warning(string $message, ?LogContext $context = null): void
    {
        $this->logs[] = ['level' => 'warning', 'message' => $message, 'context' => $context];
    }

    public function notice(string $message, ?LogContext $context = null): void
    {
        $this->logs[] = ['level' => 'notice', 'message' => $message, 'context' => $context];
    }

    public function info(string $message, ?LogContext $context = null): void
    {
        $this->logs[] = ['level' => 'info', 'message' => $message, 'context' => $context];
    }

    public function debug(string $message, ?LogContext $context = null): void
    {
        $this->logs[] = ['level' => 'debug', 'message' => $message, 'context' => $context];
    }

    public function log(LogLevel $level, string $message, ?LogContext $context = null): void
    {
        $this->logs[] = ['level' => $level->value, 'message' => $message, 'context' => $context];
    }

    public function getLogs(): array
    {
        return $this->logs;
    }

    public function hasInfo(string $message): bool
    {
        return $this->hasLevel('info', $message);
    }

    public function hasError(string $message): bool
    {
        return $this->hasLevel('error', $message);
    }

    public function hasWarning(string $message): bool
    {
        return $this->hasLevel('warning', $message);
    }

    public function hasDebug(string $message): bool
    {
        return $this->hasLevel('debug', $message);
    }

    private function hasLevel(string $level, string $message): bool
    {
        foreach ($this->logs as $log) {
            if ($log['level'] === $level && $log['message'] === $message) {
                return true;
            }
        }

        return false;
    }

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

    private ?ChannelLogger $mockChannelLogger = null;

    public ChannelLogger $security {
        get {
            return $this->mockChannelLogger ??= $this->createMockChannelLogger();
        }
    }

    public ChannelLogger $cache {
        get {
            return $this->mockChannelLogger ??= $this->createMockChannelLogger();
        }
    }

    public ChannelLogger $database {
        get {
            return $this->mockChannelLogger ??= $this->createMockChannelLogger();
        }
    }

    public ChannelLogger $framework {
        get {
            return $this->mockChannelLogger ??= $this->createMockChannelLogger();
        }
    }

    public ChannelLogger $error {
        get {
            return $this->mockChannelLogger ??= $this->createMockChannelLogger();
        }
    }

    private function createMockChannelLogger(): ChannelLogger
    {
        return new class () implements ChannelLogger {
            public function emergency(string $message, ?LogContext $context = null): void
            {
            }

            public function alert(string $message, ?LogContext $context = null): void
            {
            }

            public function critical(string $message, ?LogContext $context = null): void
            {
            }

            public function error(string $message, ?LogContext $context = null): void
            {
            }

            public function warning(string $message, ?LogContext $context = null): void
            {
            }

            public function notice(string $message, ?LogContext $context = null): void
            {
            }

            public function info(string $message, ?LogContext $context = null): void
            {
            }

            public function debug(string $message, ?LogContext $context = null): void
            {
            }
        };
    }
}

// Test transport classes
class FailingTransport implements \App\Framework\Mail\TransportInterface
{
    public function __construct(private string $errorMessage)
    {
    }

    public function send(Message $message): TransportResult
    {
        throw new \RuntimeException($this->errorMessage);
    }

    public function isAvailable(): bool
    {
        return true;
    }

    public function getName(): string
    {
        return 'Failing Transport';
    }
}

class SmtpFailingTransport implements \App\Framework\Mail\TransportInterface
{
    public function send(Message $message): TransportResult
    {
        throw SmtpException::connectionFailed('localhost', 587, 'Connection refused');
    }

    public function isAvailable(): bool
    {
        return true;
    }

    public function getName(): string
    {
        return 'SMTP Failing Transport';
    }
}
