<?php

declare(strict_types=1);

use App\Domain\Common\ValueObject\Email;
use App\Framework\CommandBus\CommandBus;
use App\Framework\Mail\Commands\SendEmailBatchCommand;
use App\Framework\Mail\Commands\SendEmailCommand;
use App\Framework\Mail\EmailList;
use App\Framework\Mail\Mailer;
use App\Framework\Mail\Message;
use App\Framework\Mail\Testing\MockTransport;
use App\Framework\Mail\TransportResult;
use App\Framework\Queue\Queue;
use App\Framework\Queue\ValueObjects\JobPayload;

describe('Mailer', function () {
    beforeEach(function () {
        $this->transport = new MockTransport();
        $this->queue = new MailTestQueue();
        $this->commandBus = new TestCommandBus();
        $this->mailer = new Mailer($this->transport, $this->queue, $this->commandBus);

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

    describe('send method', function () {
        it('sends email successfully using transport', function () {
            $result = $this->mailer->send($this->message);

            expect($result)->toBeTrue();
            expect($this->transport->getSentMessageCount())->toBe(1);
            expect($this->transport->getLastSentMessage()['message'])->toBe($this->message);
        });

        it('returns false when transport fails', function () {
            $this->transport->setShouldFail(true, 'Transport error');

            $result = $this->mailer->send($this->message);

            expect($result)->toBeFalse();
            expect($this->transport->getSentMessageCount())->toBe(0);
        });

        it('does not use queue for synchronous sending', function () {
            $this->mailer->send($this->message);

            expect($this->queue->wasUsed())->toBeFalse();
            expect($this->commandBus->wasUsed())->toBeFalse();
        });
    });

    describe('queue method', function () {
        it('dispatches SendEmailCommand with default parameters', function () {
            $result = $this->mailer->queue($this->message);

            expect($result)->toBeTrue();
            expect($this->commandBus->wasUsed())->toBeTrue();

            $command = $this->commandBus->getLastCommand();
            expect($command)->toBeInstanceOf(SendEmailCommand::class);
            expect($command->message)->toBe($this->message);
            expect($command->maxRetries)->toBe(3);
            expect($command->delaySeconds)->toBe(0);
        });

        it('dispatches SendEmailCommand with custom parameters', function () {
            $result = $this->mailer->queue($this->message, 5, 30);

            expect($result)->toBeTrue();
            expect($this->commandBus->wasUsed())->toBeTrue();

            $command = $this->commandBus->getLastCommand();
            expect($command)->toBeInstanceOf(SendEmailCommand::class);
            expect($command->message)->toBe($this->message);
            expect($command->maxRetries)->toBe(5);
            expect($command->delaySeconds)->toBe(30);
        });

        it('returns false when command dispatch fails', function () {
            $this->commandBus->setShouldFail(true);

            $result = $this->mailer->queue($this->message);

            expect($result)->toBeFalse();
        });

        it('does not use transport for queued sending', function () {
            $this->mailer->queue($this->message);

            expect($this->transport->getSentMessageCount())->toBe(0);
        });
    });

    describe('sendBatch method', function () {
        beforeEach(function () {
            $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'))
                ),
            ];
        });

        it('sends all messages successfully', function () {
            $results = $this->mailer->sendBatch($this->messages);

            expect($results)->toHaveCount(2);
            expect($results[0])->toBeTrue();
            expect($results[1])->toBeTrue();
            expect($this->transport->getSentMessageCount())->toBe(2);
        });

        it('handles mixed success and failure', function () {
            // Make transport fail on second call
            $transport = new TestTransport();
            $transport->failOn(2);
            $mailer = new Mailer($transport, $this->queue, $this->commandBus);

            $results = $mailer->sendBatch($this->messages);

            expect($results)->toHaveCount(2);
            expect($results[0])->toBeTrue();
            expect($results[1])->toBeFalse();
        });

        it('handles invalid messages in batch', function () {
            $mixedBatch = [
                $this->messages[0],
                'invalid-message',
                $this->messages[1],
            ];

            $results = $this->mailer->sendBatch($mixedBatch);

            expect($results)->toHaveCount(3);
            expect($results[0])->toBeTrue();
            expect($results[1])->toBeFalse();
            expect($results[2])->toBeTrue();
        });
    });

    describe('queueBatch method', function () {
        beforeEach(function () {
            $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'))
                ),
            ];
        });

        it('dispatches SendEmailBatchCommand for valid messages', function () {
            $results = $this->mailer->queueBatch($this->messages);

            expect($results)->toHaveCount(2);
            expect($results[0])->toBeTrue();
            expect($results[1])->toBeTrue();
            expect($this->commandBus->wasUsed())->toBeTrue();

            $command = $this->commandBus->getLastCommand();
            expect($command)->toBeInstanceOf(SendEmailBatchCommand::class);
            expect($command->messages)->toBe($this->messages);
            expect($command->maxRetries)->toBe(3);
            expect($command->delaySeconds)->toBe(0);
        });

        it('handles mixed valid and invalid messages', function () {
            $mixedBatch = [
                $this->messages[0],
                'invalid-message',
                $this->messages[1],
            ];

            $results = $this->mailer->queueBatch($mixedBatch);

            expect($results)->toHaveCount(3);
            expect($results[0])->toBeTrue();  // Valid message
            expect($results[1])->toBeFalse(); // Invalid message
            expect($results[2])->toBeTrue();  // Valid message

            $command = $this->commandBus->getLastCommand();
            expect($command->messages)->toHaveCount(2);
        });

        it('returns early for all invalid messages', function () {
            $invalidBatch = ['invalid1', 'invalid2'];

            $results = $this->mailer->queueBatch($invalidBatch);

            expect($results)->toHaveCount(2);
            expect($results[0])->toBeFalse();
            expect($results[1])->toBeFalse();
            expect($this->commandBus->wasUsed())->toBeFalse();
        });

        it('marks all valid messages as failed when batch command fails', function () {
            $this->commandBus->setShouldFail(true);

            $results = $this->mailer->queueBatch($this->messages);

            expect($results)->toHaveCount(2);
            expect($results[0])->toBeFalse();
            expect($results[1])->toBeFalse();
        });

        it('uses custom retry parameters', function () {
            $this->mailer->queueBatch($this->messages, 5, 60);

            $command = $this->commandBus->getLastCommand();
            expect($command->maxRetries)->toBe(5);
            expect($command->delaySeconds)->toBe(60);
        });
    });
});

// Test stubs
class MailTestQueue implements Queue
{
    private bool $used = false;

    private array $jobs = [];

    public function push(JobPayload $payload): void
    {
        $this->used = true;
        $this->jobs[] = $payload;
    }

    public function pop(): ?JobPayload
    {
        return array_shift($this->jobs);
    }

    public function peek(): ?JobPayload
    {
        return $this->jobs[0] ?? null;
    }

    public function size(): int
    {
        return count($this->jobs);
    }

    public function clear(): int
    {
        $count = count($this->jobs);
        $this->jobs = [];

        return $count;
    }

    public function getStats(): array
    {
        return ['size' => count($this->jobs)];
    }

    public function wasUsed(): bool
    {
        return $this->used;
    }
}

class TestCommandBus implements CommandBus
{
    private bool $used = false;

    private bool $shouldFail = false;

    private ?object $lastCommand = null;

    public function dispatch(object $command): mixed
    {
        $this->used = true;
        $this->lastCommand = $command;

        if ($this->shouldFail) {
            throw new Exception('Command dispatch failed');
        }

        return null;
    }

    public function wasUsed(): bool
    {
        return $this->used;
    }

    public function getLastCommand(): ?object
    {
        return $this->lastCommand;
    }

    public function setShouldFail(bool $shouldFail): void
    {
        $this->shouldFail = $shouldFail;
    }
}

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

    private int $failOn = 0;

    private int $callCount = 0;

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

    public function send(Message $message): TransportResult
    {
        $this->callCount++;

        if ($this->failOn > 0 && $this->callCount === $this->failOn) {
            return TransportResult::failure('Simulated failure');
        }

        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();
    }

    public function getLastSentMessage(): ?array
    {
        return $this->mockTransport->getLastSentMessage();
    }

    public function failOn(int $callNumber): void
    {
        $this->failOn = $callNumber;
    }
}
