<?php

declare(strict_types=1);

namespace FatBottom\Services;

use FatBottom\Core\Config;
use PDO;
use RuntimeException;

final class MySqlMigrator
{
    public function __construct(private readonly Config $config)
    {
    }

    public function migrate(): array
    {
        $sqlitePath = BASE_PATH . '/storage/database/store.sqlite';
        if (!is_file($sqlitePath)) {
            throw new RuntimeException('The SQLite store database does not exist yet.');
        }
        if (!extension_loaded('pdo_mysql')) {
            throw new RuntimeException('The pdo_mysql PHP extension is required for migration.');
        }

        $mysql = new PDO(
            sprintf(
                'mysql:host=%s;port=%d;charset=utf8mb4',
                $this->config->get('database.mysql_host'),
                (int) $this->config->get('database.mysql_port', 3306)
            ),
            (string) $this->config->get('database.mysql_username'),
            (string) $this->config->get('database.mysql_password'),
            [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
        );
        $database = preg_replace('/[^a-zA-Z0-9_]/', '', (string) $this->config->get('database.mysql_database'));
        if ($database === '') {
            throw new RuntimeException('Enter a valid MySQL database name.');
        }
        $mysql->exec("CREATE DATABASE IF NOT EXISTS `{$database}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
        $mysql->exec("USE `{$database}`");

        $sqlite = new PDO('sqlite:' . $sqlitePath, null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
        $tables = $sqlite->query(
            "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
        )->fetchAll(PDO::FETCH_COLUMN);
        $rowCount = 0;

        $mysql->exec('SET FOREIGN_KEY_CHECKS = 0');
        foreach ($tables as $table) {
            $columns = $sqlite->query("PRAGMA table_info(`{$table}`)")->fetchAll(PDO::FETCH_ASSOC);
            $definitions = [];
            $primary = [];
            foreach ($columns as $column) {
                $type = strtoupper((string) $column['type']);
                $mysqlType = str_contains($type, 'INT') ? 'BIGINT'
                    : (str_contains($type, 'REAL') || str_contains($type, 'FLOA') ? 'DOUBLE' : 'TEXT');
                if ((int) $column['pk'] === 1 && $column['name'] === 'id') {
                    $definitions[] = '`id` BIGINT NOT NULL AUTO_INCREMENT';
                    $primary[] = '`id`';
                } else {
                    $definitions[] = sprintf(
                        '`%s` %s %s',
                        str_replace('`', '', (string) $column['name']),
                        $mysqlType,
                        (int) $column['notnull'] === 1 ? 'NOT NULL' : 'NULL'
                    );
                    if ((int) $column['pk'] === 1) {
                        $primary[] = '`' . str_replace('`', '', (string) $column['name']) . '`';
                    }
                }
            }
            if ($primary !== []) {
                $definitions[] = 'PRIMARY KEY (' . implode(', ', $primary) . ')';
            }
            $mysql->exec("DROP TABLE IF EXISTS `{$table}`");
            $mysql->exec("CREATE TABLE `{$table}` (" . implode(', ', $definitions) . ') ENGINE=InnoDB');

            $rows = $sqlite->query("SELECT * FROM `{$table}`")->fetchAll(PDO::FETCH_ASSOC);
            if ($rows !== []) {
                $names = array_keys($rows[0]);
                $placeholders = implode(', ', array_fill(0, count($names), '?'));
                $quotedNames = implode(', ', array_map(static fn (string $name): string => "`{$name}`", $names));
                $insert = $mysql->prepare("INSERT INTO `{$table}` ({$quotedNames}) VALUES ({$placeholders})");
                foreach ($rows as $row) {
                    $insert->execute(array_values($row));
                    $rowCount++;
                }
            }
        }
        $mysql->exec('SET FOREIGN_KEY_CHECKS = 1');

        return ['tables' => count($tables), 'rows' => $rowCount];
    }
}

