<?php

declare(strict_types=1);

namespace Dealerdirect\Agreements\Tests\Factories;

use Dealerdirect\Agreements\DataObjects\SalesAgreement;
use Dealerdirect\Agreements\Exceptions\EmptyConsumerDomainsException;
use Dealerdirect\Agreements\Exceptions\EmptyEncryptionKeyException;
use Dealerdirect\Agreements\Exceptions\InvalidSiteReferenceException;
use Dealerdirect\Agreements\Exceptions\SalesAgreementNotStoredException;
use Dealerdirect\Agreements\Factories\SalesAgreementFactory;
use Dealerdirect\Agreements\Pdf\Generator;
use Dealerdirect\Agreements\Pdf\Templates\BicycleSalesAgreement;
use Dealerdirect\Agreements\Pdf\Templates\CarSalesAgreement;
use Dealerdirect\Agreements\Pdf\Templates\MarktplaatsSalesAgreement;
use Dealerdirect\Agreements\Pdf\Templates\MotorbikeSalesAgreement;
use Dealerdirect\Agreements\Tests\TestCase;
use Dealerdirect\Agreements\Tests\Traits\DataObjectFixtures;
use DealerDirect\Generic\Category\Site;
use DealerDirect\Sdk\Facade\Email;
use Illuminate\Filesystem\FilesystemAdapter;
use League\Flysystem\Filesystem;
use League\Flysystem\Local\LocalFilesystemAdapter;

/**
 * @covers \Dealerdirect\Agreements\Factories\SalesAgreementFactory
 *
 * @uses \Dealerdirect\Agreements\Pdf\Templates\AbstractTemplate
 * @uses \Dealerdirect\Agreements\Pdf\Templates\SalesAgreement
 * @uses \Dealerdirect\Agreements\DataObjects\Contact
 * @uses \Dealerdirect\Agreements\Utils\GeneralUtils
 * @uses \Dealerdirect\Agreements\Utils\Translator
 * @uses \Dealerdirect\Agreements\DataObjects\Vehicle
 * @uses \Dealerdirect\Agreements\DataObjects\Conditions
 */
final class SalesAgreementFactoryTest extends TestCase
{
    use DataObjectFixtures;

    private FilesystemAdapter $storage;

    public function setUp(): void
    {
        parent::setUp();

        $localFileSystem = new LocalFilesystemAdapter('/tmp/testing');
        $fileSystem = new Filesystem($localFileSystem);

        $this->storage = new FilesystemAdapter($fileSystem, $localFileSystem);
    }

    public function tearDown(): void
    {
        $this->storage->deleteDirectory('verkoopovereenkomsten');
        $this->storage->makeDirectory('verkoopovereenkomsten');

        parent::tearDown();
    }

    /**
     * @dataProvider provideValidSalesAgreementTemplates
     */
    public function test_method_get_sales_agreement_template(int $siteReference, string $expectedTemplate): void
    {
        $data = $this->fixtureSalesAgreement(
            siteReference: $siteReference
        );

        $this->assertSame(
            $expectedTemplate,
            get_class(SalesAgreementFactory::getSalesAgreementTemplate($data))
        );
    }

    /**
     * @covers \Dealerdirect\Agreements\Exceptions\InvalidSiteReferenceException
     */
    public function test_get_sales_agreement_template_should_throw_exception_when_unknown_site_reference(): void
    {
        $this->expectException(InvalidSiteReferenceException::class);

        $data = $this->fixtureSalesAgreement(
            siteReference: 999
        );

        SalesAgreementFactory::getSalesAgreementTemplate($data);
    }

    public function test_method_create_will_use_cache_if_exists(): void
    {
        $this->expectNotToPerformAssertions();

        $data = $this->fixtureSalesAgreement();
        $storageName = $this->generateStorageName($data);

        // Add a file to the storage
        $this->storage->put($storageName, 'pdf content');

        /** @var Generator|\PHPUnit\Framework\MockObject\MockObject $generator */
        $generator = $this->createStub(Generator::class);
        $generator->expects($this->never())->method('generate');

        $factory = new SalesAgreementFactory($data, $this->storage, $generator);

        $factory->create();
    }

    public function test_method_create_will_generate_pdf_when_cache_does_not_exist(): void
    {
        /** @var Generator|\PHPUnit\Framework\MockObject\MockObject $generator */
        $generator = $this->createStub(Generator::class);
        $generator->expects($this->once())->method('generate')->willReturn('PDF content');

        $data = $this->fixtureSalesAgreement();
        $factory = new SalesAgreementFactory($data, $this->storage, $generator);

        $factory->create();

        $this->assertSame('PDF content', $this->storage->get($this->generateStorageName($data)));
    }

    public function test_method_show_will_return_cached_pdf(): void
    {
        /** @var Generator|\PHPUnit\Framework\MockObject\MockObject $generator */
        $generator = $this->createStub(Generator::class);
        $data = $this->fixtureSalesAgreement();
        $this->storage->put($this->generateStorageName($data), 'Cached PDF content');

        $factory = new SalesAgreementFactory($this->fixtureSalesAgreement(), $this->storage, $generator);

        $this->assertSame('Cached PDF content', $factory->show());
    }

    public function test_method_show_will_return_generated_pdf(): void
    {
        /** @var Generator|\PHPUnit\Framework\MockObject\MockObject $generator */
        $generator = $this->createStub(Generator::class);
        $generator->method('generate')->willReturn('Generated PDF content');

        $factory = new SalesAgreementFactory(
            $this->fixtureSalesAgreement(),
            $this->storage,
            $generator
        );

        $this->assertSame('Generated PDF content', $factory->show());
    }

    public function test_method_delete_when_file_exists(): void
    {
        /** @var Generator|\PHPUnit\Framework\MockObject\MockObject $generator */
        $generator = $this->createStub(Generator::class);
        $data = $this->fixtureSalesAgreement();
        $storageName = $this->generateStorageName($data);

        $this->storage->put($storageName, 'To delete');

        $factory = new SalesAgreementFactory($data, $this->storage, $generator);

        $factory->delete();

        $this->storage->assertMissing($storageName);
    }

    public function test_method_delete_when_file_does_not_exists(): void
    {
        /** @var Generator|\PHPUnit\Framework\MockObject\MockObject $generator */
        $generator = $this->createStub(Generator::class);

        $factory = new SalesAgreementFactory(
            $this->fixtureSalesAgreement(),
            $this->storage,
            $generator
        );

        $factory->delete();

        $this->storage->assertMissing($factory->getStorageName());
    }

    /**
     * @covers \Dealerdirect\Agreements\Exceptions\EmptyConsumerDomainsException
     */
    public function test_method_send_should_throw_exception_when_domains_are_empty(): void
    {
        $this->expectException(EmptyConsumerDomainsException::class);

        /** @var Generator|\PHPUnit\Framework\MockObject\MockObject $generator */
        $generator = $this->createStub(Generator::class);

        $factory = new SalesAgreementFactory(
            $this->fixtureSalesAgreement(),
            $this->storage,
            $generator
        );

        /** @var Email|\PHPUnit\Framework\MockObject\MockObject $email */
        $email = $this->createStub(Email::class);

        $factory->send(
            $email,
            null,
            'encryptionKey',
        );
    }

    /**
     * @covers \Dealerdirect\Agreements\Exceptions\EmptyEncryptionKeyException
     */
    public function test_method_send_should_throw_exception_when_encryption_key_is_empty(): void
    {
        $this->expectException(EmptyEncryptionKeyException::class);

        /** @var Generator|\PHPUnit\Framework\MockObject\MockObject $generator */
        $generator = $this->createStub(Generator::class);

        $factory = new SalesAgreementFactory(
            $this->fixtureSalesAgreement(),
            $this->storage,
            $generator
        );

        /** @var Email|\PHPUnit\Framework\MockObject\MockObject $email */
        $email = $this->createStub(Email::class);

        $factory->send(
            $email,
            ['carNl' => 'ikwilvanmijnautoaf.nl.aio.test'],
            null,
        );
    }

    /**
     * @uses \Dealerdirect\Agreements\Mailers\Mailer
     * @uses \Dealerdirect\Agreements\Mailers\SalesAgreementMailer
     */
    public function test_method_send_will_send_email(): void
    {
        /** @var Generator|\PHPUnit\Framework\MockObject\MockObject $generator */
        $generator = $this->createStub(Generator::class);

        /** @var Email|\PHPUnit\Framework\MockObject\MockObject $emailClient */
        $emailClient = $this->createMock(Email::class);
        $emailClient->expects($this->once())->method('sendEmail');

        $factory = new SalesAgreementFactory(
            $this->fixtureSalesAgreement(),
            $this->storage,
            $generator
        );

        $factory->send(
            $emailClient,
            ['carNl' => 'ikwilvanmijnautoaf.nl.aio.test'],
            'EmsbBUFwIbL2Sq2vBj519AyE8Gazb2Z6nUOPHPQPnX4',
        );
    }

    /**
     * @covers \Dealerdirect\Agreements\Exceptions\SalesAgreementNotStoredException
     */
    public function test_method_store_should_throw_exception_when_storage_fails(): void
    {
        $this->expectException(SalesAgreementNotStoredException::class);

        /** @var Generator|\PHPUnit\Framework\MockObject\MockObject $generator */
        $generator = $this->createStub(Generator::class);
        $generator->method('generate')->willReturn('PDF content');

        /** @var FilesystemAdapter|\PHPUnit\Framework\MockObject\MockObject $storage */
        $storage = $this->createStub(FilesystemAdapter::class);
        $storage->method('exists')->willReturn(false);
        $storage->method('put')->willReturn(false);

        $factory = new SalesAgreementFactory(
            $this->fixtureSalesAgreement(),
            $storage,
            $generator
        );

        $factory->create();
    }

    /**
     * @return array<int,array<int,int|string>>
     */
    public static function provideValidSalesAgreementTemplates(): array
    {
        return [
            [Site::REF_CAR_NL, CarSalesAgreement::class],
            [Site::REF_CAR_BE_NL, CarSalesAgreement::class],
            [Site::REF_CAR_MARKTPLAATS_NL, MarktplaatsSalesAgreement::class],
            [Site::REF_MOTO_NL, MotorbikeSalesAgreement::class],
            [Site::REF_MOTO_BE, MotorbikeSalesAgreement::class],
            [Site::REF_MOTO_DE, MotorbikeSalesAgreement::class],
            [Site::REF_SCOOTER_NL, MotorbikeSalesAgreement::class],
            [Site::REF_BICYCLE_NL, BicycleSalesAgreement::class],
        ];
    }

    private function generateStorageName(SalesAgreement $data): string
    {
        return sprintf('verkoopovereenkomsten/%s.pdf', sha1(
            $data->company->id
            . $data->soldDate->format('Y-m-d H:i:s')
            . $data->deliverId
            . $data->vehicle->brand
        ));
    }
}
