<?php

namespace MintyPHP\Tests\Service\Mail;

use MintyPHP\Repository\Mail\MailLogRepositoryInterface;
use MintyPHP\Service\Mail\MailService;
use MintyPHP\Service\Settings\SettingsSmtpGateway;
use PHPUnit\Framework\TestCase;

class MailServiceTest extends TestCase
{
    private function createSmtpGatewayMock(): SettingsSmtpGateway
    {
        $gateway = $this->createMock(SettingsSmtpGateway::class);
        $gateway->method('getSmtpHost')->willReturn('localhost');
        $gateway->method('getSmtpPort')->willReturn(587);
        $gateway->method('getSmtpUser')->willReturn('');
        $gateway->method('getSmtpPassword')->willReturn('');
        $gateway->method('getSmtpSecure')->willReturn('tls');
        $gateway->method('getSmtpFrom')->willReturn('test@example.com');
        $gateway->method('getSmtpFromName')->willReturn('Test');

        return $gateway;
    }

    private function newService(
        ?MailLogRepositoryInterface $mailLogRepository = null,
        ?SettingsSmtpGateway $settingsSmtpGateway = null,
    ): MailService {
        return new MailService(
            $mailLogRepository ?? $this->createMock(MailLogRepositoryInterface::class),
            $settingsSmtpGateway ?? $this->createSmtpGatewayMock(),
        );
    }

    public function testSendLogsBeforeSending(): void
    {
        $mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
        $mailLogRepository->expects($this->once())
            ->method('create')
            ->with($this->callback(function (array $data): bool {
                return $data['to_email'] === 'user@example.com'
                    && $data['subject'] === 'Hello'
                    && $data['status'] === 'queued';
            }))
            ->willReturn(1);

        $service = $this->newService(mailLogRepository: $mailLogRepository);

        $service->send('user@example.com', 'Hello', '<p>Hi</p>', 'Hi');
    }

    public function testSendHandlesMailerException(): void
    {
        $mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
        $mailLogRepository->method('create')->willReturn(1);
        $mailLogRepository->expects($this->once())->method('markFailed');
        $mailLogRepository->expects($this->never())->method('markSent');

        $service = $this->newService(mailLogRepository: $mailLogRepository);

        $result = $service->send('user@example.com', 'Hello', '<p>Hi</p>', 'Hi');

        $this->assertFalse($result['ok']);
        $this->assertSame('send_failed', $result['error']);
    }

    public function testSendHandlesNullLogId(): void
    {
        $mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
        $mailLogRepository->method('create')->willReturn(null);
        $mailLogRepository->expects($this->never())->method('markFailed');
        $mailLogRepository->expects($this->never())->method('markSent');

        $service = $this->newService(mailLogRepository: $mailLogRepository);

        $result = $service->send('user@example.com', 'Hello', '<p>Hi</p>', 'Hi');

        $this->assertFalse($result['ok']);
        $this->assertSame('send_failed', $result['error']);
    }

    public function testSendWithEmptyLogIdStillReturnsError(): void
    {
        // create() returns null (e.g. DB insert failed) — error path still returns a result.
        $mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
        $mailLogRepository->method('create')->willReturn(null);

        $service = $this->newService(mailLogRepository: $mailLogRepository);

        $result = $service->send('user@example.com', 'Subject', '<p>Body</p>', 'Body');

        $this->assertArrayHasKey('ok', $result);
        $this->assertArrayHasKey('error', $result);
        $this->assertFalse($result['ok']);
    }

    public function testSendTemplateCallsSend(): void
    {
        // Templates won't exist in test context, so html/text will be empty strings.
        // But the send path is still exercised, creating a log entry.
        $mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
        $mailLogRepository->expects($this->once())
            ->method('create')
            ->with($this->callback(function (array $data): bool {
                return $data['to_email'] === 'user@example.com'
                    && $data['template'] === 'welcome';
            }))
            ->willReturn(2);

        $service = $this->newService(mailLogRepository: $mailLogRepository);

        $result = $service->sendTemplate('welcome', ['name' => 'Alice'], 'user@example.com', 'Welcome');

        $this->assertArrayHasKey('ok', $result);
    }

    public function testSendCallsMarkFailedWithErrorMessage(): void
    {
        $mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
        $mailLogRepository->method('create')->willReturn(5);
        $mailLogRepository->expects($this->once())
            ->method('markFailed')
            ->with(
                $this->identicalTo(5),
                $this->isString(),
            );

        $service = $this->newService(mailLogRepository: $mailLogRepository);

        $service->send('user@example.com', 'Test', '<p>Body</p>', 'Body');
    }

    public function testSendPassesMetaTemplateToLogEntry(): void
    {
        $mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
        $mailLogRepository->expects($this->once())
            ->method('create')
            ->with($this->callback(function (array $data): bool {
                return $data['template'] === 'invoice';
            }))
            ->willReturn(3);

        $service = $this->newService(mailLogRepository: $mailLogRepository);

        $service->send('user@example.com', 'Invoice', '<p>Invoice</p>', 'Invoice', [
            'template' => 'invoice',
        ]);
    }

    public function testSendWithoutMetaTemplateLogsNullTemplate(): void
    {
        $mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
        $mailLogRepository->expects($this->once())
            ->method('create')
            ->with($this->callback(function (array $data): bool {
                return $data['template'] === null;
            }))
            ->willReturn(4);

        $service = $this->newService(mailLogRepository: $mailLogRepository);

        $service->send('user@example.com', 'Ad hoc', '<p>Ad hoc</p>', 'Ad hoc');
    }
}
