<?php
/**
 * Copyright since 2025 InPost S.A.
 *
 * For the full license information, please view the LICENSE file bundled with the module.
 *
 * @author InPost S.A.
 * @copyright since 2025 InPost S.A.
 * @license MIT
 */

declare(strict_types=1);

namespace InPost\International\Installer\Database;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Schema\Comparator;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\Table;

if (!defined('_PS_VERSION_')) {
    exit;
}

final class SchemaUpdater
{
    /**
     * @var Connection
     */
    private $connection;

    /**
     * @var Schema|null
     */
    private $schema;

    /**
     * @var Comparator|null
     */
    private $comparator;

    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    public function migrate(MigrationInterface $migration, string $direction): void
    {
        if (!in_array($direction, ['up', 'down'], true)) {
            throw new \InvalidArgumentException(sprintf('Invalid direction "%s".', $direction));
        }

        $fromSchema = $this->getSchema();
        $toSchema = clone $fromSchema;

        $migration->$direction($toSchema);

        foreach ($this->getMigrationSQL($fromSchema, $toSchema) as $sql) {
            if (is_callable([$this->connection, 'executeStatement'])) {
                $this->connection->executeStatement($sql);
            } else {
                $this->connection->exec($sql);
            }
        }

        $this->schema = $toSchema;
    }

    private function getSchema(): Schema
    {
        return $this->schema ?? $this->schema = $this->createSchema();
    }

    /**
     * @see AbstractSchemaManager::listTableColumns() might fail if no mapping to a DBAL type is configured for a colum type.
     * To deal with such cases, if an exception occurs, we try to limit schema information only to tables essential for our migrations.
     */
    private function createSchema(): Schema
    {
        $schemaManager = $this->connection->getSchemaManager();

        try {
            return $schemaManager->createSchema();
        } catch (Exception|DBALException $e) {
            $tableNames = array_filter($schemaManager->listTableNames(), static function (string $name): bool {
                return str_contains($name, 'inpost_intl');
            });

            $tables = array_map(static function (string $name) use ($schemaManager): Table {
                return $schemaManager->listTableDetails($name);
            }, $tableNames);

            return new Schema($tables, [], $schemaManager->createSchemaConfig());
        }
    }

    /**
     * @return string[]
     */
    private function getMigrationSQL(Schema $fromSchema, Schema $toSchema): array
    {
        $this->comparator = $this->comparator ?? new Comparator();

        return $this->comparator
            ->compare($fromSchema, $toSchema)
            ->toSql($this->connection->getDatabasePlatform());
    }
}
