#!/usr/bin/env php
<?php

declare(strict_types=1);

use BenMorel\SmartDump\Driver\MySQLDriver;
use BenMorel\SmartDump\Dumper;
use BenMorel\SmartDump\Configuration\DumpConfiguration;
use BenMorel\SmartDump\Configuration\TargetTable;
use BenMorel\SmartDump\Object\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\SingleCommandApplication;

set_time_limit(0);

$possibleVendorLocations = array(
    '..',         // when used in its original location during development, in bin/
    '../..',      // when used from the Composer binary folder, in vendor/bin/
    '../../../..' // when used from a Composer vendor directory, in vendor/benmorel/smartdump/bin/
);

// Locate the Composer autoloader
$found = false;
foreach ($possibleVendorLocations as $location) {
    $autoloader = __DIR__ . '/' . $location . '/vendor/autoload.php';
    if (is_readable($autoloader)) {
        /** @psalm-suppress UnresolvableInclude */
        require $autoloader;
        $found = true;
        break;
    }
}

if (! $found) {
    echo 'Could not find the vendor/autoload.php file.' . PHP_EOL;
    echo 'Did you install your dependencies using Composer?' . PHP_EOL;
    exit(1);
}

/**
 * Creates a TargetTable from a string.
 *
 * Possible formats:
 *
 * - table
 * - db.table
 * - table:CONDITIONS
 * - db.table:CONDITIONS
 */
function createTargetTable(string $table, ?string $schema): TargetTable
{
    if (preg_match('/^(?:([^\:\.]+)\.)?([^\:\.]+)(?:\:(.+))?$/', $table, $matches) !== 1) {
        throw new InvalidArgumentException('Invalid format for target table.');
    }

    /** @psalm-var array{0: string, 1: string, 2: string, 3?: string} $matches */

    if ($matches[1] !== '') {
        // overrides the default schema provided in --database, if any
        $schema = $matches[1];
    } elseif ($schema === null) {
        throw new InvalidArgumentException('Either provide table names as schema.table, or use the --database option.');
    }

    $table = $matches[2];

    if (isset($matches[3]) && $matches[3] !== '') {
        $conditions = $matches[3];
    } else {
        $conditions = null;
    }

    $targetTable = new TargetTable();

    $targetTable->table = new Table($schema, $table);
    $targetTable->conditions = $conditions;

    return $targetTable;
}

$command = function(InputInterface $input, OutputInterface $output): void {
    /** @var string $host */
    $host = $input->getOption('host');

    /** @var string $port */
    $port = $input->getOption('port');

    /** @var string $user */
    $user = $input->getOption('user');

    /** @var string $password */
    $password = $input->getOption('password');

    /** @var string $charset */
    $charset = $input->getOption('charset');

    $dsn = sprintf('mysql:host=%s;port=%s;charset=%s', $host, $port, $charset);

    $pdo = new PDO($dsn, $user, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $driver = new MySQLDriver($pdo);
    $dumper = new Dumper($pdo, $driver);

    /** @var string|null $database */
    $database = $input->getOption('database');

    /** @var string[] $tables */
    $tables = $input->getArgument('tables');

    $targetTables = array_map(
        fn(string $table) => createTargetTable($table, $database),
        $tables
    );

    $noCreateTable = (bool) $input->getOption('no-create-table');
    $addDropTable  = (bool) $input->getOption('add-drop-table');
    $noSchemaName  = (bool) $input->getOption('no-schema-name');
    $merge         = (bool) $input->getOption('merge');

    $config = new DumpConfiguration();

    $config->targetTables              = $targetTables;
    $config->addCreateTable            = ! $noCreateTable;
    $config->addDropTable              = $addDropTable;
    $config->includeSchemaNameInOutput = ! $noSchemaName;
    $config->merge                     = $merge;

    $statements = $dumper->dump($config);

    foreach ($statements as $statement) {
        $output->writeln($statement);
    }
};

(new SingleCommandApplication())
    ->setName('Smart Dump')
    ->addArgument('tables', InputArgument::IS_ARRAY, 'The table names, separated with spaces, as schema.table, or just the table name if --database is provided')
    ->addOption('host', null, InputOption::VALUE_REQUIRED, 'The host name', 'localhost')
    ->addOption('port', null, InputOption::VALUE_REQUIRED, 'The port number', '3306')
    ->addOption('user', null, InputOption::VALUE_REQUIRED, 'The user name;', 'root')
    ->addOption('password', null, InputOption::VALUE_REQUIRED, 'The password', '')
    ->addOption('charset', null, InputOption::VALUE_REQUIRED, 'The connection charset', 'utf8mb4')
    ->addOption('database', null, InputOption::VALUE_REQUIRED, 'Accept table names as arguments, in the given database')
    ->addOption('no-create-table', null, InputOption::VALUE_NONE, 'Add this option to not include a CREATE TABLE statement')
    ->addOption('add-drop-table', null, InputOption::VALUE_NONE, 'Add this option to include a DROP TABLE IF EXISTS statement before CREATE TABLE')
    ->addOption('no-schema-name', null, InputOption::VALUE_NONE, 'Add this option to not include the schema name in the output')
    ->addOption('merge', null, InputOption::VALUE_NONE, 'Add this option to create a dump that can be merged into an existing schema, using an upsert. Implies --no-create-table')
    ->setCode($command)
    ->run();
