<?php
declare(strict_types=1);

use App\Core\Database\ConnectionFactory;
use App\Core\Support\Env;

$basePath = dirname(__DIR__);
$vendorAutoload = $basePath . '/vendor/autoload.php';

if (is_file($vendorAutoload)) {
    require $vendorAutoload;
} else {
    spl_autoload_register(static function (string $class) use ($basePath): void {
        $prefix = 'App\\';
        if (!str_starts_with($class, $prefix)) {
            return;
        }

        $relative = substr($class, strlen($prefix));
        $file = $basePath . '/src/' . str_replace('\\', '/', $relative) . '.php';
        if (is_file($file)) {
            require $file;
        }
    });
}

Env::load($basePath . '/.env');

/** @var array<string, mixed> $dbConfig */
$dbConfig = require $basePath . '/config/database.php';
$dryRun = in_array('--dry-run', $argv, true);
$useRemote = in_array('--use-remote', $argv, true);

if ($useRemote) {
    $remoteHost = (string) Env::get('DB_HOST_REMOTE', '');
    if ($remoteHost !== '') {
        $dbConfig['host'] = $remoteHost;
    }
}

$pdo = ConnectionFactory::make($dbConfig);

echo 'Fix status codes script' . PHP_EOL;
echo $dryRun ? '[mode] dry-run (no DB changes)' . PHP_EOL : '[mode] apply' . PHP_EOL;
if ($useRemote) {
    echo '[db] using DB_HOST_REMOTE for this run' . PHP_EOL;
}

try {
    $resultGroups = fixGroupCodes($pdo, $dryRun);
    $resultStatuses = fixStatusCodes($pdo, $dryRun);

    echo PHP_EOL . 'Summary:' . PHP_EOL;
    echo '  groups updated: ' . $resultGroups['updated'] . PHP_EOL;
    echo '  statuses updated: ' . $resultStatuses['updated'] . PHP_EOL;
    echo 'Done.' . PHP_EOL;
} catch (Throwable $exception) {
    fwrite(STDERR, '[error] ' . $exception->getMessage() . PHP_EOL);
    exit(1);
}

/**
 * @return array{updated:int}
 */
function fixGroupCodes(PDO $pdo, bool $dryRun): array
{
    $stmt = $pdo->query('SELECT id, name, code FROM order_status_groups ORDER BY id ASC');
    $rows = $stmt !== false ? $stmt->fetchAll(PDO::FETCH_ASSOC) : [];
    if (!is_array($rows)) {
        $rows = [];
    }

    $usedCodes = [];
    foreach ($rows as $row) {
        $existing = strtolower(trim((string) ($row['code'] ?? '')));
        if ($existing !== '') {
            $usedCodes[$existing] = true;
        }
    }

    $updated = 0;
    $updateStmt = $pdo->prepare('UPDATE order_status_groups SET code = :code, updated_at = :updated_at WHERE id = :id');

    foreach ($rows as $row) {
        $id = max(0, (int) ($row['id'] ?? 0));
        if ($id <= 0) {
            continue;
        }

        $currentCode = strtolower(trim((string) ($row['code'] ?? '')));
        $base = normalizeCodeFromName((string) ($row['name'] ?? ''), 'group_' . $id);
        $targetCode = ensureUniqueCode($base, $currentCode, $usedCodes);

        if ($targetCode === $currentCode) {
            continue;
        }

        echo '  [group #' . $id . '] ' . ($currentCode !== '' ? $currentCode : '(empty)') . ' -> ' . $targetCode . PHP_EOL;
        if (!$dryRun) {
            $updateStmt->execute([
                'id' => $id,
                'code' => $targetCode,
                'updated_at' => date('Y-m-d H:i:s'),
            ]);
        }

        if ($currentCode !== '') {
            unset($usedCodes[$currentCode]);
        }
        $usedCodes[$targetCode] = true;
        $updated++;
    }

    return ['updated' => $updated];
}

/**
 * @return array{updated:int}
 */
function fixStatusCodes(PDO $pdo, bool $dryRun): array
{
    $stmt = $pdo->query('SELECT id, name, code FROM order_statuses ORDER BY id ASC');
    $rows = $stmt !== false ? $stmt->fetchAll(PDO::FETCH_ASSOC) : [];
    if (!is_array($rows)) {
        $rows = [];
    }

    $usedCodes = [];
    foreach ($rows as $row) {
        $existing = strtolower(trim((string) ($row['code'] ?? '')));
        if ($existing !== '') {
            $usedCodes[$existing] = true;
        }
    }

    $updated = 0;
    $updateStmt = $pdo->prepare('UPDATE order_statuses SET code = :code, updated_at = :updated_at WHERE id = :id');

    foreach ($rows as $row) {
        $id = max(0, (int) ($row['id'] ?? 0));
        if ($id <= 0) {
            continue;
        }

        $currentCode = strtolower(trim((string) ($row['code'] ?? '')));
        $base = normalizeCodeFromName((string) ($row['name'] ?? ''), 'status_' . $id);
        $targetCode = ensureUniqueCode($base, $currentCode, $usedCodes);

        if ($targetCode === $currentCode) {
            continue;
        }

        echo '  [status #' . $id . '] ' . ($currentCode !== '' ? $currentCode : '(empty)') . ' -> ' . $targetCode . PHP_EOL;
        if (!$dryRun) {
            $updateStmt->execute([
                'id' => $id,
                'code' => $targetCode,
                'updated_at' => date('Y-m-d H:i:s'),
            ]);
        }

        if ($currentCode !== '') {
            unset($usedCodes[$currentCode]);
        }
        $usedCodes[$targetCode] = true;
        $updated++;
    }

    return ['updated' => $updated];
}

/**
 * @param array<string, bool> $usedCodes
 */
function ensureUniqueCode(string $base, string $currentCode, array $usedCodes): string
{
    $normalizedCurrent = strtolower(trim($currentCode));
    $candidate = $base;
    $index = 2;

    while ($candidate !== $normalizedCurrent && isset($usedCodes[$candidate])) {
        $suffix = '_' . $index;
        $candidate = mb_substr($base, 0, max(1, 64 - mb_strlen($suffix))) . $suffix;
        $index++;
    }

    return $candidate;
}

function normalizeCodeFromName(string $name, string $fallback): string
{
    $value = mb_strtolower(trim($name));
    if ($value === '') {
        $value = $fallback;
    }

    $value = strtr($value, [
        mb_chr(261, 'UTF-8') => 'a', // ą
        mb_chr(263, 'UTF-8') => 'c', // ć
        mb_chr(281, 'UTF-8') => 'e', // ę
        mb_chr(322, 'UTF-8') => 'l', // ł
        mb_chr(324, 'UTF-8') => 'n', // ń
        mb_chr(243, 'UTF-8') => 'o', // ó
        mb_chr(347, 'UTF-8') => 's', // ś
        mb_chr(378, 'UTF-8') => 'z', // ź
        mb_chr(380, 'UTF-8') => 'z', // ż
    ]);

    $value = preg_replace('/[^a-z0-9_\-\.]+/u', '_', $value) ?? '';
    $value = trim($value, '_');
    if ($value === '') {
        $value = $fallback;
    }

    return mb_substr($value, 0, 64);
}
