<?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\LegacyOrderImportMappingWizard;
use CpCreation\VitiCore\Cart\LegacyOrderImport\LegacyOrderSourceReader;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Style\SymfonyStyle;

final class LegacyOrderImportMappingWizardTest extends TestCase
{
    private Connection $target;
    private LegacyOrderImportMappingGenerator $files;
    private LegacyOrderImportMappingWizard $wizard;

    /** @var list<string> */
    private array $temporaryPaths = [];

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

    protected function tearDown(): void
    {
        foreach ($this->temporaryPaths as $path) {
            @unlink($path);
            @rmdir(\dirname($path));
        }

        $this->target->close();
    }

    public function testPaginatesTwentyFiveCandidatesAndSelectsFromTheSecondPage(): void
    {
        $sourceId = $this->uuid(101);
        $draft = $this->draft([
            'variants' => [$sourceId => LegacyOrderImportMapping::UNRESOLVED],
            '_source_variants' => [
                $sourceId => ['label' => 'Cuvee historique - Bouteille', 'orders' => 4, 'lines' => 4, 'quantity' => 24],
            ],
            '_target_variants' => $this->variantCandidates(25),
        ]);

        [$result, $saved, $display] = $this->runWizard($draft, ['n', 'p', 'n', '5']);

        self::assertSame(['updated' => 1, 'remaining' => 0, 'quit' => false], $result);
        self::assertSame($this->uuid(25), $saved['variants'][$sourceId]);
        self::assertStringContainsString('Candidat 01', $display);
        self::assertStringContainsString('Candidat 21', $display);
        self::assertStringContainsString('Candidat 25', $display);
        self::assertGreaterThanOrEqual(2, mb_substr_count($display, 'Candidat 01'));
    }

    public function testDisplaysHistoricalSourcePricesAndCurrentTargetPrice(): void
    {
        $sourceId = $this->uuid(101);
        $targetId = $this->uuid(1);
        $draft = $this->draft([
            'variants' => [$sourceId => LegacyOrderImportMapping::UNRESOLVED],
            '_source_variants' => [
                $sourceId => [
                    'label' => 'Cuvee historique - Bouteille',
                    'orders' => 2,
                    'lines' => 2,
                    'quantity' => 12,
                    'prices' => [3250, 3500],
                ],
            ],
            '_target_variants' => [
                $targetId => [
                    'label' => 'Cuvee actuelle - Bouteille',
                    'sku' => 'CUVEE-75',
                    'price' => 4100,
                    'price_discount' => 3900,
                ],
            ],
        ]);

        [, , $display] = $this->runWizard($draft, ['q']);

        self::assertStringContainsString('Prix historique', $display);
        self::assertStringContainsString('32,50 EUR / 35,00 EUR', $display);
        self::assertStringContainsString('Prix actuel', $display);
        self::assertStringContainsString('39,00 EUR (au lieu de 41,00 EUR)', $display);
    }

    public function testCanSkipGoBackAndResumeBetweenMappings(): void
    {
        $sourceA = $this->uuid(101);
        $sourceB = $this->uuid(102);
        $sourceC = $this->uuid(103);
        $draft = $this->draft([
            'variants' => [
                $sourceA => LegacyOrderImportMapping::UNRESOLVED,
                $sourceB => LegacyOrderImportMapping::UNRESOLVED,
                $sourceC => LegacyOrderImportMapping::UNRESOLVED,
            ],
            '_source_variants' => [
                $sourceA => ['label' => 'Source A', 'orders' => 1, 'lines' => 1, 'quantity' => 1],
                $sourceB => ['label' => 'Source B', 'orders' => 1, 'lines' => 1, 'quantity' => 1],
                $sourceC => ['label' => 'Source C', 'orders' => 1, 'lines' => 1, 'quantity' => 1],
            ],
            '_target_variants' => $this->variantCandidates(2),
        ]);

        [$result, $saved] = $this->runWizard($draft, ['1', 's', 'r', '2', '1']);

        self::assertSame(['updated' => 3, 'remaining' => 0, 'quit' => false], $result);
        self::assertSame($this->uuid(1), $saved['variants'][$sourceA]);
        self::assertSame($this->uuid(2), $saved['variants'][$sourceB]);
        self::assertSame($this->uuid(1), $saved['variants'][$sourceC]);
    }

    public function testDetachWritesAnExplicitNullToTheJsonFile(): void
    {
        $sourceId = $this->uuid(101);
        $draft = $this->draft([
            'variants' => [$sourceId => LegacyOrderImportMapping::UNRESOLVED],
            '_source_variants' => [
                $sourceId => ['label' => 'Cuvee disparue', 'orders' => 2, 'lines' => 2, 'quantity' => 12],
            ],
            '_target_variants' => $this->variantCandidates(1),
        ]);

        [$result, $saved] = $this->runWizard($draft, ['d']);

        self::assertSame(['updated' => 1, 'remaining' => 0, 'quit' => false], $result);
        self::assertArrayHasKey($sourceId, $saved['variants']);
        self::assertNull($saved['variants'][$sourceId]);
    }

    public function testQuitSavesProgressAndLeavesRemainingMappingsUntouched(): void
    {
        $sourceA = $this->uuid(101);
        $sourceB = $this->uuid(102);
        $draft = $this->draft([
            'variants' => [
                $sourceA => LegacyOrderImportMapping::UNRESOLVED,
                $sourceB => LegacyOrderImportMapping::UNRESOLVED,
            ],
            '_source_variants' => [
                $sourceA => ['label' => 'Source A', 'orders' => 1, 'lines' => 1, 'quantity' => 1],
                $sourceB => ['label' => 'Source B', 'orders' => 1, 'lines' => 1, 'quantity' => 1],
            ],
            '_target_variants' => $this->variantCandidates(1),
        ]);

        [$result, $saved] = $this->runWizard($draft, ['1', 'q']);

        self::assertSame(['updated' => 1, 'remaining' => 1, 'quit' => true], $result);
        self::assertSame($this->uuid(1), $saved['variants'][$sourceA]);
        self::assertSame(LegacyOrderImportMapping::UNRESOLVED, $saved['variants'][$sourceB]);
    }

    public function testPaymentAndDeliverySelectionsPersistTargetUuids(): void
    {
        $sourceDeliveryId = $this->uuid(201);
        $targetPaymentA = $this->uuid(301);
        $targetPaymentB = $this->uuid(302);
        $targetDelivery = $this->uuid(401);
        $draft = $this->draft([
            'payment_methods' => ['legacy-payment' => LegacyOrderImportMapping::UNRESOLVED],
            'delivery_methods' => [$sourceDeliveryId => LegacyOrderImportMapping::UNRESOLVED],
            '_source_payment_methods' => [
                'legacy-payment' => ['paid_orders' => 4, 'unpaid_orders' => 1],
            ],
            '_target_payment_methods' => [
                ['id' => $targetPaymentA, 'method' => 'payplug'],
                ['id' => $targetPaymentB, 'method' => 'e-transactions'],
            ],
            '_source_delivery_methods' => [
                $sourceDeliveryId => ['label' => 'Livraison historique', 'orders' => 5],
            ],
            '_target_delivery_methods' => [
                $targetDelivery => ['label' => 'Retrait au domaine'],
            ],
        ]);

        [$result, $saved] = $this->runWizard($draft, ['1', '1']);

        self::assertSame(['updated' => 2, 'remaining' => 0, 'quit' => false], $result);
        self::assertSame($targetPaymentB, $saved['payment_methods']['legacy-payment']);
        self::assertSame($targetDelivery, $saved['delivery_methods'][$sourceDeliveryId]);

        $mapping = LegacyOrderImportMapping::fromFile($this->temporaryPaths[array_key_last($this->temporaryPaths)]);
        self::assertSame($targetPaymentB, $mapping->paymentMethod('legacy-payment'));
        self::assertSame($targetDelivery, $mapping->deliveryMethod($sourceDeliveryId));
    }

    /**
     * @param array<string, mixed> $changes
     *
     * @return array<string, mixed>
     */
    private function draft(array $changes): array
    {
        return array_replace([
            'schema_version' => 1,
            'source' => [
                'source_id' => 'legacy-source',
                'source_key' => str_repeat('a', 64),
                'scope' => 'all',
            ],
            '_instructions' => [],
            'variants' => [],
            'payment_methods' => [],
            'delivery_methods' => [],
            '_source_variants' => [],
            '_target_variants' => [],
            '_source_payment_methods' => [],
            '_target_payment_methods' => [],
            '_source_delivery_methods' => [],
            '_target_delivery_methods' => [],
        ], $changes);
    }

    /**
     * @return array<string, array{label: string, sku: string, price: int, price_discount: null}>
     */
    private function variantCandidates(int $count): array
    {
        $candidates = [];

        for ($index = 1; $index <= $count; ++$index) {
            $candidates[$this->uuid($index)] = [
                'label' => \sprintf('Candidat %02d', $index),
                'sku' => \sprintf('SKU-%02d', $index),
                'price' => 3000 + $index,
                'price_discount' => null,
            ];
        }

        return $candidates;
    }

    /**
     * @param array<string, mixed> $draft
     * @param list<string>         $answers
     *
     * @return array{array{updated: int, remaining: int, quit: bool}, array<string, mixed>, string}
     */
    private function runWizard(array $draft, array $answers): array
    {
        $directory = sys_get_temp_dir().'/viti-legacy-wizard-'.bin2hex(random_bytes(8));
        self::assertTrue(mkdir($directory, 0775, true));
        $path = $directory.'/legacy-order-map.json';
        $this->temporaryPaths[] = $path;
        $this->files->write($path, $draft);

        $input = new ArrayInput([]);
        $input->setInteractive(true);
        $stream = fopen('php://memory', 'r+');
        self::assertIsResource($stream);
        self::assertNotFalse(fwrite($stream, implode("\n", $answers)."\n"));
        rewind($stream);
        $input->setStream($stream);
        $output = new BufferedOutput();
        $io = new SymfonyStyle($input, $output);

        try {
            $result = $this->wizard->run($draft, $path, $io);
        } finally {
            fclose($stream);
        }

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

        return [$result, $saved, $output->fetch()];
    }

    private function uuid(int $number): string
    {
        return \sprintf('00000000-0000-4000-8000-%012d', $number);
    }
}
