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

use InPost\International\Installer\Database\MigrationInterface;
use InPost\International\Installer\Database\SchemaUpdater;
use InPost\International\Installer\Exception\InstallerException;
use PrestaShop\PrestaShop\Core\Domain\Configuration\ShopConfigurationInterface;
use PrestaShop\PrestaShop\Core\Domain\Shop\ValueObject\ShopConstraint;
use Symfony\Contracts\Translation\TranslatorInterface;

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

final class DatabaseInstaller implements InstallerInterface, UninstallerInterface
{
    private const SCHEMA_VERSION_CONFIG_KEY = 'INPOST_INTL_DB_SCHEMA_VERSION';

    /**
     * @var TranslatorInterface
     */
    private $translator;

    /**
     * @var ShopConfigurationInterface
     */
    private $configuration;

    /**
     * @var SchemaUpdater
     */
    private $schemaUpdater;

    /**
     * @var iterable<MigrationInterface>
     */
    private $migrations;

    /**
     * @param iterable<MigrationInterface> $migrations
     */
    public function __construct(TranslatorInterface $translator, ShopConfigurationInterface $configuration, SchemaUpdater $schemaUpdater, iterable $migrations)
    {
        $this->translator = $translator;
        $this->configuration = $configuration;
        $this->schemaUpdater = $schemaUpdater;
        $this->migrations = $migrations;
    }

    public function install(\Module $module): void
    {
        $currentVersion = $this->configuration->get(self::SCHEMA_VERSION_CONFIG_KEY, '0', ShopConstraint::allShops());
        $downMigrations = [];

        foreach ($this->getSortedMigrations() as $migration) {
            if (\Tools::version_compare($currentVersion, $version = $migration->getVersion(), '>=')) {
                continue;
            }

            if (\Tools::version_compare($module->version, $version)) {
                $downMigrations[] = $migration;
            } else {
                $this->migrateUp($migration);
            }
        }

        $this->migrateDown(...$downMigrations);
    }

    public function uninstall(\Module $module, bool $keepData): void
    {
        if ($keepData) {
            return;
        }

        $this->migrateDown(...$this->getSortedMigrations());
    }

    private function getSortedMigrations(): array
    {
        $migrations = $this->migrations;

        if ($migrations instanceof \Traversable) {
            $migrations = iterator_to_array($migrations);
        }

        usort($migrations, static function (MigrationInterface $m1, MigrationInterface $m2): int {
            return (int) version_compare($m1->getVersion(), $m2->getVersion());
        });

        return $migrations;
    }

    private function migrateUp(MigrationInterface $migration): void
    {
        $this->migrate($migration, 'up', $migration->getVersion());
    }

    private function migrateDown(MigrationInterface ...$migrations): void
    {
        while (null !== $migration = array_pop($migrations)) {
            $previous = end($migrations);
            $this->migrate($migration, 'down', $previous ? $previous->getVersion() : null);
        }
    }

    private function migrate(MigrationInterface $migration, string $direction, ?string $version): void
    {
        try {
            $this->schemaUpdater->migrate($migration, $direction);

            $this->updateSchemaVersion($version);
        } catch (\Exception $e) {
            $message = 'up' === $direction
                ? $this->translator->trans('Could not migrate database schema to version {version}.', ['{version}' => $migration->getVersion()], 'Modules.Inpostinternational.Installer')
                : $this->translator->trans('Could not migrate database schema down from version {version}.', ['{version}' => $migration->getVersion()], 'Modules.Inpostinternational.Installer');

            throw new InstallerException($message, 0, $e);
        }
    }

    private function updateSchemaVersion(?string $version): void
    {
        if (null === $version) {
            $this->configuration->remove(self::SCHEMA_VERSION_CONFIG_KEY);
        } else {
            $this->configuration->set(self::SCHEMA_VERSION_CONFIG_KEY, $version, ShopConstraint::allShops());
        }
    }
}
