/**
 * Migrate Command
 * Run database migrations
 */
import { BaseCommand } from '../BaseCommand.js';
import { D1SqlMigrations } from '../d1/D1SqlMigrations.js';
import { WranglerConfig } from '../d1/WranglerConfig.js';
import { WranglerD1 } from '../d1/WranglerD1.js';
import { PromptHelper } from '../PromptHelper.js';
import { confirmProductionRun, mapConnectionToOrmConfig, parseRollbackSteps, } from '../utils/DatabaseCliUtils.js';
import { EnvFileLoader } from '../utils/EnvFileLoader.js';
import { readEnvString } from '../../common/ExternalServiceUtils.js';
import { databaseConfig } from '../../config/database.js';
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
import { Migrator } from '../../migrations/Migrator.js';
import * as path from '../../node-singletons/path.js';
import { Database } from '../../orm/Database.js';
import { DatabaseAdapterRegistry } from '../../orm/DatabaseAdapterRegistry.js';
const addMigrateOptions = (command) => {
    command
        .option('--status', 'Display migration status (applied, pending, failed)')
        .option('--fresh', 'Reset database: drop all tables and re-run all migrations')
        .option('--reset', 'Rollback all migrations to initial state')
        .option('--rollback', 'Rollback last migration batch')
        .option('--step <number>', 'Number of batches to rollback (use with --rollback)', '1')
        .option('--force', 'Skip production confirmation (allow unsafe operations in production)')
        .option('--service <domain/name>', 'Run global + service-specific migrations for microservices')
        .option('--only-service <domain/name>', 'Run only service-specific migrations (exclude global)')
        .option('--local', 'D1 only: run against local D1 database')
        .option('--remote', 'D1 only: run against remote D1 database')
        .option('--database <name>', 'D1 only: Wrangler D1 database binding name (not DB_DATABASE env var)')
        .option('--all', 'Run migrations for all configured database connections')
        .option('--no-interactive', 'Disable interactive prompts (useful for CI/CD)');
};
const getInteractive = (options) => options['interactive'] !== false;
const getMigrationDirs = () => {
    const fromEnv = readEnvString('MIGRATIONS_GLOBAL_DIR', databaseConfig.migrations.directory);
    const globalDir = fromEnv.trim() === '' ? databaseConfig.migrations.directory : fromEnv;
    const extension = databaseConfig.migrations.extension;
    const separateTrackingRaw = readEnvString('MIGRATIONS_SEPARATE_TRACKING', '').trim();
    const separateTracking = separateTrackingRaw === '1' || separateTrackingRaw.toLowerCase() === 'true';
    return { globalDir, extension, separateTracking };
};
const getServiceArgs = (options) => {
    let serviceArg;
    if (typeof options['onlyService'] === 'string') {
        serviceArg = String(options['onlyService']);
    }
    else if (typeof options['service'] === 'string') {
        serviceArg = String(options['service']);
    }
    const includeGlobal = typeof options['onlyService'] !== 'string';
    return { service: serviceArg, includeGlobal };
};
const isDestructiveAction = (options) => options['fresh'] === true || options['reset'] === true || options['rollback'] === true;
const isWranglerD1Driver = (driver) => driver === 'd1';
const describeTargetDatabase = (conn) => {
    switch (conn.driver) {
        case 'sqlite':
            return `sqlite:${conn.database}`;
        case 'postgresql':
            return `postgresql:${conn.host}:${conn.port}/${conn.database}`;
        case 'mysql':
            return `mysql:${conn.host}:${conn.port}/${conn.database}`;
        case 'sqlserver':
            return `sqlserver:${conn.host}:${conn.port}/${conn.database}`;
        default:
            return `${conn.driver}`;
    }
};
const printStatus = (cmd, rows) => {
    if (rows.length === 0) {
        cmd.info('No migrations found.');
        return;
    }
    for (const row of rows) {
        const normalizedStatus = typeof row.status === 'string' ? row.status : null;
        const tag = normalizedStatus ?? (row.applied ? 'applied' : 'pending');
        const hasMeta = normalizedStatus === 'completed' ||
            normalizedStatus === 'failed' ||
            normalizedStatus === 'running' ||
            row.applied;
        const extra = hasMeta ? ` (batch=${row.batch ?? '?'}, at=${row.appliedAt ?? '?'})` : '';
        cmd.info(`${tag}: ${row.name}${extra}`);
    }
};
const logAppliedMigrations = (cmd, appliedNames) => {
    cmd.info('Applied migrations:');
    for (const name of appliedNames) {
        cmd.info(`✓ ${name}`);
    }
};
const handleStatusAction = async (migrator, cmd, driver) => {
    cmd.info(`Adapter: ${driver}`);
    const rows = await migrator.status();
    printStatus(cmd, rows);
};
const handleFreshAction = async (migrator, cmd, interactive) => {
    cmd.warn('This will drop all tables and re-run migrations.');
    if (interactive) {
        const confirmed = await PromptHelper.confirm('Continue?', false, interactive);
        if (!confirmed) {
            cmd.warn('Cancelled.');
            return;
        }
    }
    const result = await migrator.fresh();
    cmd.success(`Fresh migration completed (applied=${result.applied})`);
};
const handleResetAction = async (migrator, cmd, interactive) => {
    cmd.warn('This will rollback ALL migrations.');
    if (interactive) {
        const confirmed = await PromptHelper.confirm('Continue?', false, interactive);
        if (!confirmed) {
            cmd.warn('Cancelled.');
            return;
        }
    }
    const result = await migrator.resetAll();
    cmd.success(`All migrations reset (rolledBack=${result.rolledBack})`);
};
const handleRollbackAction = async (migrator, cmd, options) => {
    const steps = parseRollbackSteps(options);
    const result = await migrator.rollbackLastBatch(steps);
    cmd.success(`Migrations rolled back (rolledBack=${result.rolledBack})`);
};
const handleMigrateAction = async (migrator, cmd) => {
    const result = await migrator.migrate();
    if (result.applied === 0) {
        cmd.success(`Migrations completed successfully (applied=${result.applied})`);
        return;
    }
    const appliedNames = Array.isArray(result.appliedNames)
        ? result.appliedNames
        : [];
    if (appliedNames.length > 0) {
        logAppliedMigrations(cmd, appliedNames);
    }
    cmd.success(`Migrations completed successfully (applied=${result.applied})`);
};
const runMigratorActions = async (migrator, options, cmd, interactive, driver) => {
    if (options['status'] === true) {
        await handleStatusAction(migrator, cmd, driver);
        return;
    }
    if (options['fresh'] === true) {
        await handleFreshAction(migrator, cmd, interactive);
        return;
    }
    if (options['reset'] === true) {
        await handleResetAction(migrator, cmd, interactive);
        return;
    }
    if (options['rollback'] === true) {
        await handleRollbackAction(migrator, cmd, options);
        return;
    }
    await handleMigrateAction(migrator, cmd);
};
const runD1Actions = async (params) => {
    const { options, cmd, projectRoot, globalDir, extension, separateTracking, service, includeGlobal, } = params;
    if (options['status'] === true ||
        options['fresh'] === true ||
        options['reset'] === true ||
        options['rollback'] === true) {
        throw ErrorFactory.createCliError('This project is configured for D1. Only applying migrations is supported here. Use `zin d1:migrate --local|--remote` (and Wrangler subcommands) for status/rollback/reset.');
    }
    if (separateTracking) {
        cmd.warn('Note: MIGRATIONS_SEPARATE_TRACKING is ignored for D1 (Wrangler owns tracking).');
    }
    const isLocal = options['local'] === true || options['remote'] !== true;
    let dbName = 'zintrust_db';
    if (typeof options['database'] === 'string' && options['database'].trim() !== '') {
        dbName = options['database'].trim();
    }
    else {
        const resolution = WranglerConfig.resolveD1Database(projectRoot);
        if (resolution.status === 'resolved') {
            dbName = WranglerConfig.getDefaultD1DatabaseName(projectRoot) ?? 'zintrust_db';
        }
        else if (resolution.status === 'ambiguous') {
            throw ErrorFactory.createCliError('Multiple D1 targets are configured. Re-run with --database <database_name|binding> to choose the intended Wrangler D1 target.');
        }
    }
    const migrationsRelDir = WranglerConfig.getD1MigrationsDir(projectRoot, dbName);
    const outputDir = path.join(projectRoot, migrationsRelDir);
    cmd.info(`Generating D1 SQL migrations into ${migrationsRelDir}...`);
    const generated = await D1SqlMigrations.compileAndWrite({
        projectRoot,
        globalDir,
        extension,
        service,
        includeGlobal,
        outputDir,
    });
    cmd.info(`Generated ${generated.length} SQL migration file(s).`);
    cmd.info(`Running D1 migrations for ${dbName} (${isLocal ? 'local' : 'remote'})...`);
    const output = WranglerD1.applyMigrations({ cmd, dbName, isLocal });
    if (output !== '')
        cmd.info(output);
    cmd.success('D1 migrations completed successfully');
};
const logOptionsForConn = (cmd, conn, options) => {
    if (isWranglerD1Driver(conn.driver)) {
        cmd.debug(`Migrate command executed with options: ${JSON.stringify(options)}`);
        return;
    }
    const optionsForLog = { ...options };
    delete optionsForLog['database'];
    delete optionsForLog['local'];
    delete optionsForLog['remote'];
    cmd.debug(`Migrate command executed with options: ${JSON.stringify(optionsForLog)}`);
};
const warnIfAdapterMissing = (cmd, conn) => {
    if (conn.driver === 'mysql' && DatabaseAdapterRegistry.get('mysql') === undefined) {
        cmd.warn('MySQL adapter is not installed/registered; migrations may not hit a real MySQL DB.');
        cmd.warn('Install via `zin plugin install adapter:mysql` (or `zin add db:mysql`).');
        cmd.debug('[debug] Expected a side-effect import in src/zintrust.plugins.ts like: import "../../../packages/db-mysql/src/register";');
    }
    if (conn.driver === 'postgresql' && DatabaseAdapterRegistry.get('postgresql') === undefined) {
        cmd.warn('PostgreSQL adapter is not installed/registered; migrations may not hit a real PostgreSQL DB.');
        cmd.warn('Install via `zin plugin install adapter:postgres` (or `zin add db:postgres`).');
    }
};
const withD1RemoteSqlMode = async (fn) => {
    if (typeof process === 'undefined' || process.env === undefined)
        return fn();
    const prev = process.env['D1_REMOTE_MODE'];
    process.env['D1_REMOTE_MODE'] = 'sql';
    try {
        return await fn();
    }
    finally {
        if (prev === undefined) {
            delete process.env['D1_REMOTE_MODE'];
        }
        else {
            process.env['D1_REMOTE_MODE'] = prev;
        }
    }
};
const runOrmMigrationsForConn = async (params) => {
    const { conn, options, cmd, interactive, globalDir, extension, separateTracking, service, includeGlobal, } = params;
    const ormConfig = mapConnectionToOrmConfig(conn);
    const destructive = isDestructiveAction(options);
    const force = options['force'] === true;
    const okToProceed = await confirmProductionRun({
        cmd,
        interactive,
        destructive,
        force,
        message: 'NODE_ENV=production. Continue running migrations?',
    });
    if (!okToProceed)
        return;
    cmd.info(`[i] Target database: ${describeTargetDatabase(conn)}`);
    cmd.info('[i] Migration tracking table: migrations');
    cmd.debug(`[debug] Registered database adapters: ${DatabaseAdapterRegistry.list().join(', ')}`);
    warnIfAdapterMissing(cmd, conn);
    const db = Database.create(ormConfig);
    await db.connect();
    try {
        const migrator = Migrator.create({
            db,
            projectRoot: process.cwd(),
            globalDir,
            extension,
            separateTracking,
            service,
            includeGlobal,
        });
        await runMigratorActions(migrator, options, cmd, interactive, conn.driver);
    }
    finally {
        await db.disconnect();
    }
};
const processConnection = async (conn, options, cmd, interactive) => {
    // Avoid confusion: `--database` is a D1-only option (Wrangler binding name), not DB_DATABASE.
    logOptionsForConn(cmd, conn, options);
    const { globalDir, extension, separateTracking } = getMigrationDirs();
    const { service, includeGlobal } = getServiceArgs(options);
    if (isWranglerD1Driver(conn.driver)) {
        await runD1Actions({
            options,
            cmd,
            projectRoot: process.cwd(),
            globalDir,
            extension,
            separateTracking,
            service,
            includeGlobal,
        });
        return;
    }
    const run = async () => runOrmMigrationsForConn({
        conn,
        options,
        cmd,
        interactive,
        globalDir,
        extension,
        separateTracking,
        service,
        includeGlobal,
    });
    if (conn.driver === 'd1-remote') {
        cmd.info('[i] d1-remote migrations: using SQL mode automatically for this CLI run');
        await withD1RemoteSqlMode(run);
        return;
    }
    await run();
};
const executeMigrate = async (options, cmd) => {
    // Ensure .env overlays are loaded for CLI runs (needed for d1-remote signing fallbacks).
    EnvFileLoader.ensureLoaded();
    const interactive = getInteractive(options);
    const targets = [];
    if (options['all'] === true) {
        for (const [name, config] of Object.entries(databaseConfig.connections)) {
            targets.push({ name, config });
        }
    }
    else {
        targets.push({ name: 'default', config: databaseConfig.getConnection() });
    }
    // Run sequentially (to preserve ordering and interactive prompts), but avoid `await` inside a loop.
    let sequence = Promise.resolve();
    for (const { name, config } of targets) {
        sequence = sequence.then(async () => {
            if (targets.length > 1) {
                cmd.info(`\n--- Connection: ${name} (${config.driver}) ---`);
            }
            await processConnection(config, options, cmd, interactive);
        });
    }
    await sequence;
};
/**
 * Migrate Command Factory
 */
export const MigrateCommand = Object.freeze({
    /**
     * Create a new migrate command instance
     */
    create() {
        const cmd = BaseCommand.create({
            name: 'migrate',
            description: 'Run database migrations',
            addOptions: addMigrateOptions,
            execute: async (options) => executeMigrate(options, cmd),
        });
        return cmd;
    },
});
