<?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\LogLevel;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Mail\Commands\SendEmailBatchCommand;
use App\Framework\Mail\Commands\SendEmailBatchCommandHandler;
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('SendEmailBatchCommandHandler', function () {
    beforeEach(function () {
        $this->transport = new MockTransport();
        $this->logger = new BatchTestLogger();
        $this->handler = new SendEmailBatchCommandHandler($this->transport, $this->logger);

        $this->messages = [
            new Message(
                from: new Email('sender@example.com'),
                subject: 'Test 1',
                body: 'Body 1',
                to: new EmailList(new Email('recipient1@example.com'))
            ),
            new Message(
                from: new Email('sender@example.com'),
                subject: 'Test 2',
                body: 'Body 2',
                to: new EmailList(new Email('recipient2@example.com'))
            ),
        ];

        $this->command = new SendEmailBatchCommand($this->messages);
    });

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

        expect($this->transport->getSentMessageCount())->toBe(2);
        expect($this->logger->hasInfo('Starting batch email sending'))->toBeTrue();
        expect($this->logger->hasDebug('Batch email sent successfully'))->toBeTrue();
        expect($this->logger->hasInfo('Batch email sending completed'))->toBeTrue();
    });

    it('handles partial batch failure (under 50%)', function () {
        // Create transport that fails on second message only
        $failingTransport = new PartialFailingTransport();

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

        // Should not throw exception for partial failure
        $handler->handle($this->command);

        expect($failingTransport->getSentMessageCount())->toBe(1);
        expect($this->logger->hasInfo('Starting batch email sending'))->toBeTrue();
        expect($this->logger->hasDebug('Batch email sent successfully'))->toBeTrue(); // First success
        expect($this->logger->hasWarning('Batch email failed'))->toBeTrue(); // Second failure
        expect($this->logger->hasInfo('Batch email sending completed'))->toBeTrue();
    });

    it('handles high failure rate (over 50%) with warning', function () {
        $this->transport->setShouldFail(true, 'All messages fail');

        expect(fn () => $this->handler->handle($this->command))
            ->toThrow(SmtpException::class, 'All 2 emails in batch failed to send');

        expect($this->logger->hasInfo('Starting batch email sending'))->toBeTrue();
        expect($this->logger->hasWarning('Batch email failed'))->toBeTrue();
        // Note: High failure rate warning is not logged when all messages fail (exception is thrown before that)
        expect($this->logger->hasInfo('Batch email sending completed'))->toBeTrue();
    });

    it('throws exception when all messages fail', function () {
        $this->transport->setShouldFail(true, 'Transport unavailable');

        expect(fn () => $this->handler->handle($this->command))
            ->toThrow(SmtpException::class, 'All 2 emails in batch failed to send');

        expect($this->logger->hasInfo('Starting batch email sending'))->toBeTrue();
        expect($this->logger->hasWarning('Batch email failed'))->toBeTrue();
        // Note: High failure rate warning is not logged when all messages fail (exception is thrown before that)
    });

    it('handles transport exceptions in batch', function () {
        // Create transport that throws exception
        $failingTransport = new ExceptionThrowingTransport();

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

        // Should not throw for partial failure
        $handler->handle($this->command);

        expect($failingTransport->getSentMessageCount())->toBe(1);
        expect($this->logger->hasInfo('Starting batch email sending'))->toBeTrue();
        expect($this->logger->hasDebug('Batch email sent successfully'))->toBeTrue(); // First success
        expect($this->logger->hasError('Batch email exception'))->toBeTrue(); // Second exception
    });

    it('logs detailed batch statistics', function () {
        // Mixed success/failure scenario
        $mixedTransport = new MixedResultTransport();

        $handler = new SendEmailBatchCommandHandler($mixedTransport, $this->logger);
        $handler->handle($this->command);

        $logs = $this->logger->getLogs();
        $completionLog = end($logs); // Last log should be completion
        expect($completionLog['context']['total_messages'])->toBe(2);
        expect($completionLog['context']['successful'])->toBe(1);
        expect($completionLog['context']['failed'])->toBe(1);
        expect($completionLog['context']['success_rate'])->toBe(50.0);
    });

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

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

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

        $logs = $this->logger->getLogs();
        // Find debug logs (successful sends)
        $debugLogs = array_filter($logs, fn ($log) => $log['level'] === 'debug');

        expect(count($debugLogs))->toBe(2);

        $firstDebug = array_values($debugLogs)[0];
        expect($firstDebug['context']['batch_index'])->toBe(0);
        expect($firstDebug['context']['to'])->toBe('recipient1@example.com');
        expect($firstDebug['context']['subject'])->toBe('Test 1');

        $secondDebug = array_values($debugLogs)[1];
        expect($secondDebug['context']['batch_index'])->toBe(1);
        expect($secondDebug['context']['to'])->toBe('recipient2@example.com');
        expect($secondDebug['context']['subject'])->toBe('Test 2');
    });

    it('handles empty batch gracefully', function () {
        $emptyCommand = new SendEmailBatchCommand([]);

        $this->handler->handle($emptyCommand);

        expect($this->transport->getSentMessageCount())->toBe(0);
        expect($this->logger->hasInfo('Starting batch email sending'))->toBeTrue();
        expect($this->logger->hasInfo('Batch email sending completed'))->toBeTrue();

        $logs = $this->logger->getLogs();
        $completionLog = end($logs);
        expect($completionLog['context']['total_messages'])->toBe(0);
        expect($completionLog['context']['successful'])->toBe(0);
        expect($completionLog['context']['failed'])->toBe(0);
        expect($completionLog['context']['success_rate'])->toBe(0);
    });
});

// Test stub for batch handler test
class BatchTestLogger implements \App\Framework\Logging\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 for batch handler
class PartialFailingTransport implements \App\Framework\Mail\TransportInterface
{
    private MockTransport $mockTransport;

    private int $callCount = 0;

    public function __construct()
    {
        $this->mockTransport = new MockTransport();
    }

    public function send(Message $message): TransportResult
    {
        $this->callCount++;
        if ($this->callCount === 2) {
            return TransportResult::failure('Second message failed');
        }

        return $this->mockTransport->send($message);
    }

    public function isAvailable(): bool
    {
        return $this->mockTransport->isAvailable();
    }

    public function getName(): string
    {
        return $this->mockTransport->getName();
    }

    public function getSentMessageCount(): int
    {
        return $this->mockTransport->getSentMessageCount();
    }
}

class ExceptionThrowingTransport implements \App\Framework\Mail\TransportInterface
{
    private MockTransport $mockTransport;

    private int $callCount = 0;

    public function __construct()
    {
        $this->mockTransport = new MockTransport();
    }

    public function send(Message $message): TransportResult
    {
        $this->callCount++;
        if ($this->callCount === 1) {
            return $this->mockTransport->send($message); // First succeeds
        }

        throw new \RuntimeException('Transport exception');
    }

    public function isAvailable(): bool
    {
        return $this->mockTransport->isAvailable();
    }

    public function getName(): string
    {
        return $this->mockTransport->getName();
    }

    public function getSentMessageCount(): int
    {
        return $this->mockTransport->getSentMessageCount();
    }
}

class MixedResultTransport implements \App\Framework\Mail\TransportInterface
{
    private MockTransport $mockTransport;

    private int $callCount = 0;

    public function __construct()
    {
        $this->mockTransport = new MockTransport();
    }

    public function send(Message $message): TransportResult
    {
        $this->callCount++;
        if ($this->callCount === 1) {
            return $this->mockTransport->send($message); // Success
        }

        return TransportResult::failure('Second fails'); // Failure
    }

    public function isAvailable(): bool
    {
        return $this->mockTransport->isAvailable();
    }

    public function getName(): string
    {
        return $this->mockTransport->getName();
    }

    public function getSentMessageCount(): int
    {
        return $this->mockTransport->getSentMessageCount();
    }
}
