<?php

declare(strict_types=1);

namespace Dealerdirect\Agreements\Tests\Factories;

use Dealerdirect\Agreements\Enums\DealerdirectLanguage;
use Dealerdirect\Agreements\Enums\Locale;
use Dealerdirect\Agreements\Exceptions\PurchaseAgreementNotStoredException;
use Dealerdirect\Agreements\Factories\PurchaseAgreementFactory;
use Dealerdirect\Agreements\Pdf\Generator as PdfGenerator;
use Dealerdirect\Agreements\Tests\TestCase;
use Dealerdirect\Agreements\Tests\Traits\DataObjectFixtures;
use Dealerdirect\Agreements\Utils\Translator;
use Dealerdirect\Generic\Enums\SiteReference;
use DealerDirect\Sdk\Facade\Email;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Response;
use Illuminate\Filesystem\FilesystemAdapter;
use League\Flysystem\Filesystem;
use League\Flysystem\Local\LocalFilesystemAdapter;

/**
 * @covers \Dealerdirect\Agreements\Factories\PurchaseAgreementFactory
 *
 * @uses \Dealerdirect\Agreements\DataObjects\Contact
 * @uses \Dealerdirect\Agreements\DataObjects\PurchaseAgreement
 * @uses \Dealerdirect\Agreements\Enums\Locale
 * @uses \Dealerdirect\Agreements\Utils\GeneralUtils
 * @uses \Dealerdirect\Agreements\DataObjects\Vehicle
 * @uses \Dealerdirect\Agreements\Pdf\Generator
 * @uses \Dealerdirect\Agreements\Pdf\Templates\PurchaseAgreement
 * @uses \Dealerdirect\Agreements\Pdf\Templates\AbstractTemplate
 * @uses \Dealerdirect\Agreements\Utils\Translator
 * @uses \Dealerdirect\Agreements\Utils\VehicleData
 * @uses \Dealerdirect\Agreements\DataObjects\Conditions
 */
final class PurchaseAgreementFactoryTest 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('purchase-agreements');
        $this->storage->makeDirectory('purchase-agreements');

        parent::tearDown();
    }

    public function test_method_create_stores_pdf_in_storage(): void
    {
        $client = $this->createStub(Client::class);
        $client->method('send')->willReturn(
            new Response(200, ['Content-Type' => 'application/pdf'], 'pdf-content')
        );

        $factory = $this->getFactory();
        $factory->create();

        $this->assertTrue(
            $this->storage->exists($factory->getStorageName())
        );
    }

    public function test_method_create_will_not_store_pdf_in_storage_if_already_exists_old_storage_name(): void
    {
        $data = self::fixturePurchaseAgreement(
            soldDate: new \DateTime('2023-01-01'),
        );

        foreach (DealerdirectLanguage::cases() as $language) {
            $storageName = $this->generateStorageName($data->company->id, $data->lotId, $data->vehicleType, $language);

            $this->storage->put($storageName, 'pdf-content');
        }

        $client = $this->createMock(Client::class);
        $client->expects($this->never())->method('send');

        $this->getFactory($data)->create();
    }

    public function test_method_create_will_not_store_pdf_in_storage_if_already_exists(): void
    {
        $data = self::fixturePurchaseAgreement(
            soldDate: new \DateTime('2025-09-29'),
        );

        foreach (DealerdirectLanguage::cases() as $language) {
            $storageName = $this->generateStorageName(
                $data->company->id,
                $data->lotId,
                $data->vehicleType,
                $language,
                $data->siteReference,
                $data->deliverId,
            );

            $this->storage->put($storageName, 'pdf-content');
        }

        $client = $this->createMock(Client::class);
        $client->expects($this->never())->method('send');

        $this->getFactory($data)->create();
    }

    public function test_method_show_will_return_cached_pdf_old_storage_name(): void
    {
        $data = self::fixturePurchaseAgreement(
            soldDate: new \DateTime('2023-01-01'),
        );

        foreach (DealerdirectLanguage::cases() as $language) {
            $storageName = $this->generateStorageName($data->company->id, $data->lotId, $data->vehicleType, $language);

            $this->storage->put($storageName, 'Cached PDF content');
        }

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

    public function test_method_show_will_return_cached_pdf(): void
    {
        $data = self::fixturePurchaseAgreement(
            soldDate: new \DateTime('2025-09-29'),
        );

        foreach (DealerdirectLanguage::cases() as $language) {
            $storageName = $this->generateStorageName(
                $data->company->id,
                $data->lotId,
                $data->vehicleType,
                $language,
                $data->siteReference,
                $data->deliverId,
            );

            $this->storage->put($storageName, 'Cached PDF content');
        }

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

    public function test_method_show_will_return_generated_pdf(): void
    {
        $generator = $this->createStub(PdfGenerator::class);
        $generator->method('generate')->willReturn('Generated PDF content');

        /** @var PdfGenerator $generator */
        $factory = $this->getFactory(generator: $generator);

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

    public function test_method_delete_will_remove_pdf_from_storage_old_storage_name(): void
    {
        $data = self::fixturePurchaseAgreement(
            soldDate: new \DateTime('2023-01-01'),
        );

        foreach (DealerdirectLanguage::cases() as $language) {
            $storageName = $this->generateStorageName($data->company->id, $data->lotId, $data->vehicleType, $language);

            $this->storage->put($storageName, 'pdf-content');
        }

        $this->getFactory($data)->delete();

        $this->assertFalse($this->storage->exists($storageName));
    }

    public function test_method_delete_will_remove_pdf_from_storage(): void
    {
        $data = self::fixturePurchaseAgreement(
            soldDate: new \DateTime('2025-09-29'),
        );

        foreach (DealerdirectLanguage::cases() as $language) {
            $storageName = $this->generateStorageName(
                $data->company->id,
                $data->lotId,
                $data->vehicleType,
                $language,
                $data->siteReference,
                $data->deliverId,
            );

            $this->storage->put($storageName, 'pdf-content');
        }

        $this->getFactory($data)->delete();

        $this->assertFalse($this->storage->exists($storageName));
    }

    /**
     * @uses \Dealerdirect\Agreements\Mailers\Mailer
     * @uses \Dealerdirect\Agreements\Mailers\PurchaseAgreementMailer
     * @uses \Dealerdirect\Agreements\Utils\Translator
     */
    public function test_method_send_will_send_email_with_pdf(): void
    {
        $client = $this->createStub(Client::class);
        $client->method('send')->willReturn(
            new Response(200, ['Content-Type' => 'application/pdf'], 'Generated PDF content')
        );

        $emailClient = $this->createMock(Email::class);
        $emailClient->expects($this->once())->method('sendEmail');

        /** @var Email $emailClient */
        $this->getFactory()->send($emailClient);
    }

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

        $client = $this->createStub(Client::class);
        $client->method('send')->willReturn(
            new Response(200, ['Content-Type' => 'application/pdf'], 'pdf-content')
        );

        $storage = $this->createStub(FilesystemAdapter::class);
        $storage->method('directories')->willReturn([]);
        $storage->method('exists')->willReturn(false);
        $storage->method('put')->willReturn(false);

        /** @var FilesystemAdapter $storage */
        $this->getFactory(storage: $storage)->create();
    }

    private function getFactory(
        ?\Dealerdirect\Agreements\DataObjects\PurchaseAgreement $data = null,
        ?PdfGenerator $generator = null,
        ?DealerdirectLanguage $language = null,
        ?FilesystemAdapter $storage = null
    ): PurchaseAgreementFactory {
        if (null === $generator) {
            $template = new \Dealerdirect\Agreements\Pdf\Templates\PurchaseAgreement(
                Locale::NL_NL,
                $data ?? self::fixturePurchaseAgreement(),
                Translator::create(SiteReference::CAR_NL->value),
            );

            /** @var \Cloudinary\Cloudinary $cloudinary */
            $cloudinary = $this->createStub(\Cloudinary\Cloudinary::class);
            $template->setCloudinary($cloudinary);

            $generator = PdfGenerator::fromTemplate($template);
        }

        return new PurchaseAgreementFactory(
            $data ?? self::fixturePurchaseAgreement(),
            $storage ?? $this->storage,
            $generator,
            $language ?? DealerdirectLanguage::NL,
        );
    }

    private function generateStorageName(
        int $companyId,
        int $lotId,
        string $vehicleType,
        DealerdirectLanguage $language,
        ?int $siteReference = null,
        ?int $deliverId = null,
    ): string {
        if (null !== $deliverId) {
            return sprintf(
                'purchase-agreements/v2-%s.pdf',
                sha1($siteReference . $deliverId . $language->value),
            );
        }

        return sprintf(
            'purchase-agreements/%s.pdf',
            sha1($companyId . $lotId . $vehicleType . $language->value),
        );
    }
}
