<?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\Command;

use CpCreation\VitiCore\Bridge\Symfony\Bundle\Command\ImportLegacyOrdersCommand;
use CpCreation\VitiCore\Cart\LegacyOrderImport\DbalLegacyOrderSourceConnector;
use CpCreation\VitiCore\Cart\LegacyOrderImport\LegacyOrderImporter;
use CpCreation\VitiCore\Cart\LegacyOrderImport\LegacyOrderImportMappingGenerator;
use CpCreation\VitiCore\Cart\LegacyOrderImport\LegacyOrderImportMappingWizard;
use CpCreation\VitiCore\Cart\LegacyOrderImport\LegacyOrderSourceConnection;
use CpCreation\VitiCore\Cart\LegacyOrderImport\LegacyOrderSourceConnectionConfig;
use CpCreation\VitiCore\Cart\LegacyOrderImport\LegacyOrderSourceConnector;
use CpCreation\VitiCore\Cart\LegacyOrderImport\LegacyOrderSourceReader;
use CpCreation\VitiCore\Cart\LegacyOrderImport\LegacyPricingSnapshotBuilder;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Tester\CommandTester;

final class ImportLegacyOrdersCommandTest extends TestCase
{
    public function testUsesTheProjectMappingFileByDefault(): void
    {
        [$target, $command] = $this->createCommand(new DbalLegacyOrderSourceConnector());

        try {
            self::assertSame(
                'config/legacy-order-map.json',
                $command->getDefinition()->getOption('mapping')->getDefault(),
            );
            self::assertTrue($command->getDefinition()->hasOption('configure-mapping'));
            self::assertFalse($command->getDefinition()->hasOption('source-dsn-env'));
            self::assertSame('127.0.0.1', $command->getDefinition()->getOption('host')->getDefault());
            self::assertSame(3306, $command->getDefinition()->getOption('port')->getDefault());
            self::assertSame(22, $command->getDefinition()->getOption('ssh-port')->getDefault());
            foreach (['database', 'user', 'password', 'ssh-host', 'ssh-user', 'ssh-key'] as $option) {
                self::assertTrue($command->getDefinition()->hasOption($option));
            }
        } finally {
            $target->close();
        }
    }

    public function testBuildsADirectMySqlConfigurationFromOptions(): void
    {
        $connector = new CapturingLegacyOrderSourceConnector();
        [$target, $command] = $this->createCommand($connector);
        $tester = new CommandTester($command);

        try {
            $status = $tester->execute([
                '--database' => 'legacy_shop',
                '--user' => 'legacy_user',
                '--password' => ' secret with spaces ',
                '--host' => 'mysql.example.test',
                '--port' => '3307',
            ], ['interactive' => false]);

            self::assertSame(Command::FAILURE, $status);
            self::assertStringContainsString(CapturingLegacyOrderSourceConnector::STOP_MESSAGE, $tester->getDisplay());
            self::assertNotNull($connector->config);
            self::assertSame('legacy_shop', $connector->config->database);
            self::assertSame('legacy_user', $connector->config->user);
            self::assertSame(' secret with spaces ', $connector->config->password);
            self::assertSame('mysql.example.test', $connector->config->host);
            self::assertSame(3307, $connector->config->port);
            self::assertFalse($connector->config->usesSsh());
        } finally {
            $target->close();
        }
    }

    public function testBuildsAnSshTunnelConfigurationWithoutRequiringAMySqlHost(): void
    {
        $connector = new CapturingLegacyOrderSourceConnector();
        [$target, $command] = $this->createCommand($connector);
        $tester = new CommandTester($command);

        try {
            $status = $tester->execute([
                '--database' => 'legacy_shop',
                '--user' => 'legacy_user',
                '--password' => 'mysql-secret',
                '--port' => '3308',
                '--ssh-host' => 'server2.cpcreation.fr',
                '--ssh-user' => 'ssh_user',
                '--ssh-key' => 'keys/server2',
                '--ssh-port' => '2222',
            ], ['interactive' => false]);

            self::assertSame(Command::FAILURE, $status);
            self::assertNotNull($connector->config);
            self::assertSame(LegacyOrderSourceConnectionConfig::DEFAULT_HOST, $connector->config->host);
            self::assertSame(3308, $connector->config->port);
            self::assertSame('server2.cpcreation.fr', $connector->config->sshHost);
            self::assertSame('ssh_user', $connector->config->sshUser);
            self::assertSame('/project/keys/server2', $connector->config->sshKey);
            self::assertSame(2222, $connector->config->sshPort);
            self::assertTrue($connector->config->usesSsh());
        } finally {
            $target->close();
        }
    }

    public function testPromptsForRequiredMySqlCredentialsInteractively(): void
    {
        $connector = new CapturingLegacyOrderSourceConnector();
        [$target, $command] = $this->createCommand($connector);
        $tester = new CommandTester($command);
        $tester->setInputs(['legacy_shop', 'legacy_user', 'mysql-secret']);

        try {
            $status = $tester->execute([]);

            self::assertSame(Command::FAILURE, $status);
            self::assertNotNull($connector->config);
            self::assertSame('legacy_shop', $connector->config->database);
            self::assertSame('legacy_user', $connector->config->user);
            self::assertSame('mysql-secret', $connector->config->password);
        } finally {
            $target->close();
        }
    }

    public function testPromptsForSshCredentialsWithoutPromptingForAMySqlHost(): void
    {
        $connector = new CapturingLegacyOrderSourceConnector();
        [$target, $command] = $this->createCommand($connector);
        $tester = new CommandTester($command);
        $tester->setInputs(['ssh_user', '/root/.ssh/server2']);

        try {
            $status = $tester->execute([
                '--database' => 'legacy_shop',
                '--user' => 'legacy_user',
                '--password' => 'mysql-secret',
                '--ssh-host' => 'server2.cpcreation.fr',
            ]);

            self::assertSame(Command::FAILURE, $status);
            self::assertNotNull($connector->config);
            self::assertSame(LegacyOrderSourceConnectionConfig::DEFAULT_HOST, $connector->config->host);
            self::assertSame('ssh_user', $connector->config->sshUser);
            self::assertSame('/root/.ssh/server2', $connector->config->sshKey);
        } finally {
            $target->close();
        }
    }

    public function testRejectsMissingCredentialsWithoutInteraction(): void
    {
        $connector = new CapturingLegacyOrderSourceConnector();
        [$target, $command] = $this->createCommand($connector);
        $tester = new CommandTester($command);

        try {
            self::assertSame(Command::FAILURE, $tester->execute([], ['interactive' => false]));
            self::assertStringContainsString('Renseignez --database.', $tester->getDisplay());
            self::assertNull($connector->config);
        } finally {
            $target->close();
        }
    }

    /**
     * @return array{Connection, ImportLegacyOrdersCommand}
     */
    private function createCommand(LegacyOrderSourceConnector $sourceConnector): array
    {
        $target = DriverManager::getConnection([
            'driver' => 'pdo_sqlite',
            'memory' => true,
        ]);
        $sourceReader = new LegacyOrderSourceReader();
        $command = new ImportLegacyOrdersCommand(
            new LegacyOrderImporter($target, $sourceReader, new LegacyPricingSnapshotBuilder()),
            $mappingGenerator = new LegacyOrderImportMappingGenerator($target, $sourceReader),
            new LegacyOrderImportMappingWizard($mappingGenerator),
            $sourceConnector,
            '/project',
        );

        return [$target, $command];
    }
}

final class CapturingLegacyOrderSourceConnector implements LegacyOrderSourceConnector
{
    public const STOP_MESSAGE = 'Connexion interrompue par le test.';

    public ?LegacyOrderSourceConnectionConfig $config = null;

    public function connect(LegacyOrderSourceConnectionConfig $config): LegacyOrderSourceConnection
    {
        $this->config = $config;

        throw new \RuntimeException(self::STOP_MESSAGE);
    }
}
