<?php

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Tests\Cart\LegacyOrderImport;

use CpCreation\VitiCore\Cart\LegacyOrderImport\LegacyOrderImportMapping;
use CpCreation\VitiCore\Cart\LegacyOrderImport\LegacyOrderImportMappingGenerator;
use CpCreation\VitiCore\Cart\LegacyOrderImport\LegacyOrderSourceReader;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Platforms\MySQL80Platform;
use PHPUnit\Framework\TestCase;

final class LegacyOrderImportMappingGeneratorTest extends TestCase
{
    private const string MATCHING_VARIANT_ID = '11111111-1111-4111-8111-111111111111';
    private const string UNRESOLVED_VARIANT_ID = '22222222-2222-4222-8222-222222222222';
    private const string MATCHING_DELIVERY_ID = '33333333-3333-4333-8333-333333333333';
    private const string UNRESOLVED_DELIVERY_ID = '44444444-4444-4444-8444-444444444444';

    private Connection $target;
    private Connection $source;
    private LegacyOrderImportMappingGenerator $generator;

    protected function setUp(): void
    {
        $this->target = DriverManager::getConnection([
            'driver' => 'pdo_sqlite',
            'memory' => true,
        ]);
        $this->createTargetSchema();
        $this->source = $this->sourceConnection();
        $this->generator = new LegacyOrderImportMappingGenerator(
            $this->target,
            new LegacyOrderSourceReader(),
        );
    }

    protected function tearDown(): void
    {
        $this->target->close();
    }

    public function testGeneratesEverySourceIdentifierAndOnlyPrefillsCertainSameIdentityMatches(): void
    {
        $draft = $this->generator->generate($this->source, 'all', 'legacy-source', 'source-key');

        self::assertSame(self::MATCHING_VARIANT_ID, $draft['variants'][self::MATCHING_VARIANT_ID]);
        self::assertSame(
            LegacyOrderImportMapping::UNRESOLVED,
            $draft['variants'][self::UNRESOLVED_VARIANT_ID],
            'Un libelle ressemblant ou renomme ne suffit jamais a rattacher deux UUID differents.',
        );
        self::assertSame(
            '55555555-5555-4555-8555-555555555555',
            $draft['payment_methods']['e-transactions'],
        );
        self::assertSame(LegacyOrderImportMapping::UNRESOLVED, $draft['payment_methods']['check']);
        self::assertSame(self::MATCHING_DELIVERY_ID, $draft['delivery_methods'][self::MATCHING_DELIVERY_ID]);
        self::assertSame(
            LegacyOrderImportMapping::UNRESOLVED,
            $draft['delivery_methods'][self::UNRESOLVED_DELIVERY_ID],
        );

        self::assertSame(
            'Les Lys Extra brut - Bouteille (75 cl)',
            $draft['_source_variants'][self::UNRESOLVED_VARIANT_ID]['label'],
        );
        self::assertSame([3250], $draft['_source_variants'][self::UNRESOLVED_VARIANT_ID]['prices']);
        self::assertSame(3000, $draft['_target_variants'][self::MATCHING_VARIANT_ID]['price']);
        self::assertSame(2750, $draft['_target_variants'][self::MATCHING_VARIANT_ID]['price_discount']);
        self::assertStringContainsString(
            LegacyOrderImportMapping::UNRESOLVED,
            json_encode($draft['_instructions'], \JSON_THROW_ON_ERROR),
        );
        self::assertCount(2, $draft['variants'], 'Une variante repetee dans plusieurs lignes ne doit apparaitre qu\'une fois.');
    }

    public function testRefreshesVariantPriceContextWithoutChangingExistingChoices(): void
    {
        $draft = $this->generator->generate($this->source, 'all', 'legacy-source', 'source-key');
        $draft['variants'][self::UNRESOLVED_VARIANT_ID] = null;
        unset($draft['_source_variants'][self::UNRESOLVED_VARIANT_ID]['prices']);
        unset(
            $draft['_target_variants'][self::MATCHING_VARIANT_ID]['price'],
            $draft['_target_variants'][self::MATCHING_VARIANT_ID]['price_discount'],
        );

        $refreshed = $this->generator->refreshVariantContext($this->source, 'all', $draft);

        self::assertNull($refreshed['variants'][self::UNRESOLVED_VARIANT_ID]);
        self::assertSame([3250], $refreshed['_source_variants'][self::UNRESOLVED_VARIANT_ID]['prices']);
        self::assertSame(3000, $refreshed['_target_variants'][self::MATCHING_VARIANT_ID]['price']);
        self::assertSame(2750, $refreshed['_target_variants'][self::MATCHING_VARIANT_ID]['price_discount']);
    }

    public function testWriteCreatesTheParentDirectoryAndProducesAReadableMapping(): void
    {
        $root = sys_get_temp_dir().'/viti-legacy-generated-map-'.bin2hex(random_bytes(8));
        $path = $root.'/config/legacy-order-map.json';
        $draft = $this->generator->generate($this->source, 'paid', 'legacy-source', 'source-key');

        try {
            $this->generator->write($path, $draft);

            self::assertFileExists($path);
            $contents = file_get_contents($path);
            self::assertIsString($contents);
            self::assertIsArray(json_decode($contents, true, 512, \JSON_THROW_ON_ERROR));

            $mapping = LegacyOrderImportMapping::fromFile($path);
            self::assertSame(self::MATCHING_VARIANT_ID, $mapping->variant(self::MATCHING_VARIANT_ID));
            self::assertSame(
                LegacyOrderImportMapping::UNRESOLVED,
                $mapping->variant(self::UNRESOLVED_VARIANT_ID),
            );
        } finally {
            @unlink($path);
            @rmdir($root.'/config');
            @rmdir($root);
        }
    }

    public function testWriteNeverOverwritesAnExistingMapping(): void
    {
        $path = tempnam(sys_get_temp_dir(), 'viti-existing-legacy-map-');
        self::assertNotFalse($path);
        self::assertNotFalse(file_put_contents($path, "mapping existant\n"));

        try {
            $this->expectException(\RuntimeException::class);
            $this->expectExceptionMessage('existe deja');

            $this->generator->write($path, [
                'variants' => [self::UNRESOLVED_VARIANT_ID => LegacyOrderImportMapping::UNRESOLVED],
                '_source_variants' => [],
                'payment_methods' => [],
                'delivery_methods' => [],
                '_instructions' => [],
            ]);
        } finally {
            self::assertSame("mapping existant\n", file_get_contents($path));
            @unlink($path);
        }
    }

    private function createTargetSchema(): void
    {
        $this->target->executeStatement(<<<'SQL'
            CREATE TABLE product_product (
                id VARCHAR(36) NOT NULL PRIMARY KEY,
                sku VARCHAR(255) DEFAULT NULL,
                deleted_at DATETIME DEFAULT NULL
            )
            SQL);
        $this->target->executeStatement(<<<'SQL'
            CREATE TABLE product_type (
                id VARCHAR(36) NOT NULL PRIMARY KEY,
                volume NUMERIC DEFAULT NULL,
                unity VARCHAR(32) DEFAULT NULL
            )
            SQL);
        $this->target->executeStatement(<<<'SQL'
            CREATE TABLE product_variant (
                id VARCHAR(36) NOT NULL PRIMARY KEY,
                product_id VARCHAR(36) DEFAULT NULL,
                product_type_id VARCHAR(36) DEFAULT NULL,
                sku VARCHAR(255) DEFAULT NULL,
                stock_at DATETIME DEFAULT NULL,
                deleted_at DATETIME DEFAULT NULL
            )
            SQL);
        $this->target->executeStatement(<<<'SQL'
            CREATE TABLE product_price_group (
                id VARCHAR(36) NOT NULL PRIMARY KEY,
                by_default BOOLEAN NOT NULL DEFAULT 0,
                position INTEGER DEFAULT NULL,
                deleted_at DATETIME DEFAULT NULL
            )
            SQL);
        $this->target->executeStatement(<<<'SQL'
            CREATE TABLE product_price (
                id VARCHAR(36) NOT NULL PRIMARY KEY,
                group_id VARCHAR(36) DEFAULT NULL,
                variant_id VARCHAR(36) DEFAULT NULL,
                price INTEGER NOT NULL,
                price_discount INTEGER DEFAULT NULL,
                deleted_at DATETIME DEFAULT NULL
            )
            SQL);
        $this->target->executeStatement(<<<'SQL'
            CREATE TABLE product_product_translation (
                translatable_id VARCHAR(36) NOT NULL,
                locale VARCHAR(10) NOT NULL,
                name VARCHAR(255) DEFAULT NULL,
                quality VARCHAR(255) DEFAULT NULL
            )
            SQL);
        $this->target->executeStatement(<<<'SQL'
            CREATE TABLE product_type_translation (
                translatable_id VARCHAR(36) NOT NULL,
                locale VARCHAR(10) NOT NULL,
                name VARCHAR(255) DEFAULT NULL
            )
            SQL);
        $this->target->executeStatement(<<<'SQL'
            CREATE TABLE cart_payment_method (
                id VARCHAR(36) NOT NULL PRIMARY KEY,
                method VARCHAR(255) NOT NULL,
                disabled BOOLEAN NOT NULL DEFAULT 0,
                deleted_at DATETIME DEFAULT NULL
            )
            SQL);
        $this->target->executeStatement(<<<'SQL'
            CREATE TABLE shop_delivery_method (
                id VARCHAR(36) NOT NULL PRIMARY KEY,
                deleted_at DATETIME DEFAULT NULL
            )
            SQL);
        $this->target->executeStatement(<<<'SQL'
            CREATE TABLE shop_delivery_method_translation (
                translatable_id VARCHAR(36) NOT NULL,
                locale VARCHAR(10) NOT NULL,
                name VARCHAR(255) DEFAULT NULL
            )
            SQL);

        $this->target->insert('product_product', [
            'id' => 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa',
        ]);
        $this->target->insert('product_type', [
            'id' => 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb',
            'volume' => 75,
            'unity' => 'cl',
        ]);
        $this->target->insert('product_variant', [
            'id' => self::MATCHING_VARIANT_ID,
            'product_id' => 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa',
            'product_type_id' => 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb',
        ]);
        $this->target->insert('product_price_group', [
            'id' => 'cccccccc-cccc-4ccc-8ccc-cccccccccccc',
            'by_default' => 1,
            'position' => 0,
        ]);
        $this->target->insert('product_price', [
            'id' => 'dddddddd-dddd-4ddd-8ddd-dddddddddddd',
            'group_id' => 'cccccccc-cccc-4ccc-8ccc-cccccccccccc',
            'variant_id' => self::MATCHING_VARIANT_ID,
            'price' => 3000,
            'price_discount' => 2750,
        ]);
        $this->target->insert('product_product_translation', [
            'translatable_id' => 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa',
            'locale' => 'fr',
            'name' => 'Produit cible',
            'quality' => 'Brut',
        ]);
        $this->target->insert('product_type_translation', [
            'translatable_id' => 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb',
            'locale' => 'fr',
            'name' => 'Bouteille',
        ]);
        $this->target->insert('cart_payment_method', [
            'id' => '55555555-5555-4555-8555-555555555555',
            'method' => 'e-transactions',
            'disabled' => 0,
        ]);
        $this->target->insert('shop_delivery_method', [
            'id' => self::MATCHING_DELIVERY_ID,
        ]);
    }

    private function sourceConnection(): Connection
    {
        $source = $this->createMock(Connection::class);
        $source->method('getDatabasePlatform')->willReturn(new MySQL80Platform());
        $source->method('isTransactionActive')->willReturn(true);
        $source->method('fetchFirstColumn')->willReturn($this->sourceSchemaColumns());
        $source->method('fetchAllAssociative')
            ->willReturnCallback(function (string $sql): array {
                if (str_contains($sql, 'FROM cart_cart_item item')) {
                    return $this->sourceLines();
                }

                if (str_contains($sql, 'FROM cart_cart_checkout checkout')) {
                    return $this->sourceOrders();
                }

                self::fail('Requete source inattendue pendant la generation du mapping : '.$sql);
            });

        return $source;
    }

    /**
     * The source reader only verifies that every table contains its required columns.
     * Returning the complete legacy column vocabulary for each inspected table keeps
     * this unit test independent from MySQL while preserving that validation branch.
     *
     * @return list<string>
     */
    private function sourceSchemaColumns(): array
    {
        return [
            'id', 'token', 'shipping', 'shipping_name', 'group_code', 'delivery_method_json', 'promo', 'promo_id',
            'promo_value_amount', 'promo_value_percent', 'promo_with_shipping_fees', 'degressive_value', 'view_ht',
            'tax', 'comment', 'delivery_method_id', 'invoice_address_id', 'delivery_address_id', 'created_at', 'updated_at',
            'cart_cart_id', 'payment_method_id', 'payed_at', 'payed_token', 'cgv', 'delivery', 'locale', 'response',
            'colissimo_carrier', 'colissimo_code', 'private_carrier', 'ip_client', 'disabled', 'deleted_at',
            'product_variant_id', 'quantity', 'price', 'company', 'tva_number', 'lastname', 'firstname', 'street',
            'postal_code', 'city', 'country', 'email', 'phone', 'different_address', 'method', 'product_id',
            'product_type_id', 'volume', 'unity', 'equivalency', 'translatable_id', 'name', 'quality',
        ];
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function sourceLines(): array
    {
        return [
            [
                'source_item_id' => '66666666-6666-4666-8666-666666666661',
                'source_cart_id' => '77777777-7777-4777-8777-777777777771',
                'source_variant_id' => self::MATCHING_VARIANT_ID,
                'quantity' => 1,
                'price' => '3000',
                'source_product_id' => '88888888-8888-4888-8888-888888888881',
                'source_type_id' => '99999999-9999-4999-8999-999999999991',
                'volume' => '75',
                'unity' => 'cl',
                'product_name' => 'Cuvee historique identique',
                'product_quality' => 'Brut',
                'type_name' => 'Bouteille',
            ],
            [
                'source_item_id' => '66666666-6666-4666-8666-666666666662',
                'source_cart_id' => '77777777-7777-4777-8777-777777777772',
                'source_variant_id' => self::UNRESOLVED_VARIANT_ID,
                'quantity' => 2,
                'price' => '3250',
                'source_product_id' => '88888888-8888-4888-8888-888888888882',
                'source_type_id' => '99999999-9999-4999-8999-999999999992',
                'volume' => '75',
                'unity' => 'cl',
                'product_name' => 'Les Lys',
                'product_quality' => 'Extra brut',
                'type_name' => 'Bouteille',
            ],
            [
                'source_item_id' => '66666666-6666-4666-8666-666666666663',
                'source_cart_id' => '77777777-7777-4777-8777-777777777773',
                'source_variant_id' => self::UNRESOLVED_VARIANT_ID,
                'quantity' => 1,
                'price' => '3250',
                'source_product_id' => '88888888-8888-4888-8888-888888888882',
                'source_type_id' => '99999999-9999-4999-8999-999999999992',
                'volume' => '75',
                'unity' => 'cl',
                'product_name' => 'Les Lys',
                'product_quality' => 'Extra brut',
                'type_name' => 'Bouteille',
            ],
        ];
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function sourceOrders(): array
    {
        return [
            [
                'source_payment_method' => 'e-transactions',
                'source_delivery_method_id' => self::MATCHING_DELIVERY_ID,
            ],
            [
                'source_payment_method' => 'check',
                'source_delivery_method_id' => self::UNRESOLVED_DELIVERY_ID,
            ],
        ];
    }
}
