<?php

namespace App\Tests\Service;

use App\Service\MailerService;
use App\Service\UnsubscribeManager;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class MailerServiceTest extends TestCase
{
    private MessageBusInterface $bus;
    private UnsubscribeManager $unsubscribeManager;
    private EntityManagerInterface $em;
    private UrlGeneratorInterface $urlGenerator;
    private string $projectDir;

    protected function setUp(): void
    {
        $this->bus = $this->createMock(MessageBusInterface::class);
        $this->unsubscribeManager = $this->createMock(UnsubscribeManager::class);
        $this->em = $this->createMock(EntityManagerInterface::class);
        $this->urlGenerator = $this->createMock(UrlGeneratorInterface::class);
        $this->projectDir = sys_get_temp_dir().'/mailer_test_'.uniqid();
        mkdir($this->projectDir.'/public', 0o777, true);
        mkdir($this->projectDir.'/config/cert', 0o777, true);
    }

    protected function tearDown(): void
    {
        @unlink($this->projectDir.'/public/key.asc');
        @unlink($this->projectDir.'/config/cert/certificate.pem');
        @unlink($this->projectDir.'/config/cert/private-key.pem');
        @rmdir($this->projectDir.'/config/cert');
        @rmdir($this->projectDir.'/config');
        @rmdir($this->projectDir.'/public');
        @rmdir($this->projectDir);
    }

    private function createService(): MailerService
    {
        return new MailerService(
            $this->bus,
            $this->projectDir,
            'passphrase',
            'contact@test.com',
            $this->urlGenerator,
            $this->unsubscribeManager,
            $this->em,
        );
    }

    public function testGetAdminEmail(): void
    {
        $service = $this->createService();
        self::assertSame('contact@test.com', $service->getAdminEmail());
    }

    public function testGetAdminFrom(): void
    {
        $service = $this->createService();
        self::assertSame('E-Ticket <contact@test.com>', $service->getAdminFrom());
    }

    public function testSendEmailSkipsUnsubscribedRecipient(): void
    {
        $this->unsubscribeManager->method('isUnsubscribed')->willReturn(true);
        $this->bus->expects(self::never())->method('dispatch');

        $this->createService()->sendEmail('user@example.com', 'Subject', '<p>Body</p>');
    }

    public function testSendEmailDoesNotSkipWhitelistedAddress(): void
    {
        $this->unsubscribeManager->method('isUnsubscribed')->willReturn(true);
        $this->urlGenerator->method('generate')->willReturn('https://example.com/track/abc');
        $this->em->expects(self::once())->method('persist');
        $this->em->expects(self::once())->method('flush');
        $this->bus->expects(self::once())->method('dispatch')->willReturn(new Envelope(new \stdClass()));

        $this->createService()->sendEmail('contact@test.com', 'Subject', '<p>Body</p>');
    }

    public function testSendEmailDispatchesForNonUnsubscribedUser(): void
    {
        $this->unsubscribeManager->method('isUnsubscribed')->willReturn(false);
        $this->unsubscribeManager->method('generateToken')->willReturn('token123');
        $this->urlGenerator->method('generate')->willReturn('https://example.com/url');
        $this->em->expects(self::once())->method('persist');
        $this->em->expects(self::once())->method('flush');
        $this->bus->expects(self::once())->method('dispatch')->willReturn(new Envelope(new \stdClass()));

        $this->createService()->sendEmail('user@example.com', 'Test', '<p>Content</p>');
    }

    public function testSendEmailWithoutUnsubscribeHeaders(): void
    {
        $this->urlGenerator->method('generate')->willReturn('https://example.com/url');
        $this->em->expects(self::once())->method('persist');
        $this->em->expects(self::once())->method('flush');
        $this->bus->expects(self::once())->method('dispatch')->willReturn(new Envelope(new \stdClass()));

        $this->createService()->sendEmail('user@example.com', 'Test', '<p>Content</p>', withUnsubscribe: false);
    }

    public function testSendEmailWithReplyTo(): void
    {
        $this->unsubscribeManager->method('isUnsubscribed')->willReturn(false);
        $this->unsubscribeManager->method('generateToken')->willReturn('token');
        $this->urlGenerator->method('generate')->willReturn('https://example.com/url');
        $this->em->expects(self::once())->method('persist');
        $this->em->expects(self::once())->method('flush');
        $this->bus->expects(self::once())->method('dispatch')->willReturn(new Envelope(new \stdClass()));

        $this->createService()->sendEmail('user@example.com', 'Test', '<p>Content</p>', replyTo: 'reply@example.com');
    }

    public function testSendEmailWithAttachments(): void
    {
        $tmpFile = $this->projectDir.'/public/test.txt';
        file_put_contents($tmpFile, 'test content');

        $this->unsubscribeManager->method('isUnsubscribed')->willReturn(false);
        $this->unsubscribeManager->method('generateToken')->willReturn('token');
        $this->urlGenerator->method('generate')->willReturn('https://example.com/url');
        $this->em->expects(self::once())->method('persist');
        $this->em->expects(self::once())->method('flush');
        $this->bus->expects(self::once())->method('dispatch')->willReturn(new Envelope(new \stdClass()));

        $this->createService()->sendEmail('user@example.com', 'Test', '<p>Content</p>', attachments: [
            ['path' => $tmpFile, 'name' => 'test.txt'],
        ]);

        @unlink($tmpFile);
    }

    public function testSendAttachesPublicKey(): void
    {
        file_put_contents($this->projectDir.'/public/key.asc', 'fake-pgp-key');

        $this->unsubscribeManager->method('isUnsubscribed')->willReturn(false);
        $this->unsubscribeManager->method('generateToken')->willReturn('token');
        $this->urlGenerator->method('generate')->willReturn('https://example.com/url');
        $this->em->expects(self::once())->method('persist');
        $this->em->expects(self::once())->method('flush');
        $this->bus->expects(self::once())->method('dispatch')->willReturn(new Envelope(new \stdClass()));

        $this->createService()->sendEmail('user@example.com', 'Test', '<p>Content</p>');
    }

    public function testSendSignsWithSmime(): void
    {
        if (!\extension_loaded('openssl')) {
            self::markTestSkipped('OpenSSL extension required');
        }

        $privKey = openssl_pkey_new(['private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA]);
        $csr = openssl_csr_new(['commonName' => 'test'], $privKey);
        $cert = openssl_csr_sign($csr, null, $privKey, 1);

        openssl_x509_export($cert, $certPem);
        openssl_pkey_export($privKey, $keyPem, 'testpass');

        file_put_contents($this->projectDir.'/config/cert/certificate.pem', $certPem);
        file_put_contents($this->projectDir.'/config/cert/private-key.pem', $keyPem);

        $service = new MailerService(
            $this->bus,
            $this->projectDir,
            'testpass',
            'contact@test.com',
            $this->urlGenerator,
            $this->unsubscribeManager,
            $this->em,
        );

        $this->unsubscribeManager->method('isUnsubscribed')->willReturn(false);
        $this->unsubscribeManager->method('generateToken')->willReturn('token');
        $this->urlGenerator->method('generate')->willReturn('https://example.com/url');
        $this->em->expects(self::once())->method('persist');
        $this->em->expects(self::once())->method('flush');
        $this->bus->expects(self::once())->method('dispatch')->willReturn(new Envelope(new \stdClass()));

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