import { Env } from '../../config/env.js';
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
import { QueryBuilder } from '../QueryBuilder.js';
function nowIso() {
    // MySQL/MariaDB DATETIME does not accept ISO8601 with timezone (e.g. trailing 'Z').
    // Use a portable UTC datetime string.
    return new Date().toISOString().slice(0, 19).replace('T', ' ');
}
const toSafeService = (service) => {
    if (typeof service !== 'string')
        return '';
    return service.length > 0 ? service : '';
};
const assertDbSupportsMigrations = (db) => {
    const t = db.getType();
    if (t === 'd1') {
        throw ErrorFactory.createCliError('This project is configured for D1. Use `zin d1:migrate --local|--remote` for now.');
    }
};
const hasMigrationsTableSupport = (adapter) => {
    return (typeof adapter.ensureMigrationsTable === 'function');
};
const requireMigrationsTableSupport = (adapter) => {
    if (!hasMigrationsTableSupport(adapter)) {
        const isSqlProxyEnabled = Env.getBool('USE_POSTGRES_PROXY', false) ||
            Env.getBool('USE_MYSQL_PROXY', false) ||
            Env.getBool('USE_SQLSERVER_PROXY', false) ||
            Env.get('POSTGRES_PROXY_URL', '').trim() !== '' ||
            Env.get('MYSQL_PROXY_URL', '').trim() !== '' ||
            Env.get('SQLSERVER_PROXY_URL', '').trim() !== '';
        const hint = isSqlProxyEnabled
            ? 'If you are using SQL proxy adapters, ensure the proxy stack is running (e.g. `zin cp up` or `docker compose -f docker-compose.proxy.yml up -d`).'
            : undefined;
        let message = 'Migrations tracking is not supported for this database adapter yet.';
        if (hint)
            message = `${message} ${hint}`;
        throw ErrorFactory.createCliError(message);
    }
    return async () => {
        await adapter.ensureMigrationsTable();
    };
};
export const MigrationStore = Object.freeze({
    async ensureTable(db) {
        assertDbSupportsMigrations(db);
        const adapter = db.getAdapterInstance(false);
        const ensure = requireMigrationsTableSupport(adapter);
        // getAdapterInstance(false) returns a raw adapter without going through Database.query()
        // which auto-connects; ensure we're connected before creating the migrations table.
        if (typeof db.connect === 'function') {
            await db.connect();
        }
        else if (typeof adapter.connect === 'function') {
            await adapter.connect();
        }
        await ensure();
    },
    async getLastCompletedBatch(db, scope = 'global', service = '') {
        assertDbSupportsMigrations(db);
        const normalizedService = toSafeService(service);
        const row = await QueryBuilder.create('migrations', db)
            .max('batch', 'max_batch')
            .where('status', '=', 'completed')
            .andWhere('scope', '=', scope)
            .andWhere('service', '=', normalizedService)
            .first();
        const value = row?.max_batch;
        return typeof value === 'number' && Number.isFinite(value) ? value : 0;
    },
    async getAppliedMap(db, scope, service) {
        assertDbSupportsMigrations(db);
        const normalizedService = toSafeService(service);
        const rows = await QueryBuilder.create('migrations', db)
            .select('name', 'scope', 'service', 'batch', 'status')
            .selectAs('applied_at', 'appliedAt')
            .where('scope', '=', scope)
            .andWhere('service', '=', normalizedService)
            .get();
        const map = new Map();
        for (const r of rows) {
            if (typeof r.name === 'string' && r.name.length > 0) {
                map.set(r.name, {
                    ...r,
                    service: toSafeService(r.service),
                });
            }
        }
        return map;
    },
    async insertRunning(db, params) {
        assertDbSupportsMigrations(db);
        const normalizedService = toSafeService(params.service);
        const existing = await QueryBuilder.create('migrations', db)
            .select('id')
            .where('name', '=', params.name)
            .andWhere('scope', '=', params.scope)
            .andWhere('service', '=', normalizedService)
            .first();
        // Allow re-running previously failed/running migrations by updating the existing row.
        // This avoids tripping the UNIQUE(name, scope, service) constraint.
        if (existing?.id !== undefined && existing.id !== null) {
            await QueryBuilder.create('migrations', db)
                .where('name', '=', params.name)
                .andWhere('scope', '=', params.scope)
                .andWhere('service', '=', normalizedService)
                .update({
                batch: params.batch,
                status: 'running',
                applied_at: null,
            });
            return;
        }
        await QueryBuilder.create('migrations', db).insert({
            name: params.name,
            scope: params.scope,
            service: normalizedService,
            batch: params.batch,
            status: 'running',
            applied_at: null,
            created_at: nowIso(),
        });
    },
    async markStatus(db, params) {
        assertDbSupportsMigrations(db);
        const builder = QueryBuilder.create('migrations', db)
            .where('name', '=', params.name)
            .andWhere('scope', '=', params.scope)
            .andWhere('service', '=', toSafeService(params.service));
        if (params.appliedAt !== undefined) {
            await builder.update({ status: params.status, applied_at: params.appliedAt });
            return;
        }
        await builder.update({ status: params.status });
    },
    async listCompletedInBatchesGte(db, params) {
        assertDbSupportsMigrations(db);
        const rows = await QueryBuilder.create('migrations', db)
            .select('name', 'batch')
            .where('status', '=', 'completed')
            .andWhere('scope', '=', params.scope)
            .andWhere('service', '=', toSafeService(params.service))
            .andWhere('batch', '>=', params.minBatch)
            .orderBy('batch', 'DESC')
            .orderBy('id', 'DESC')
            .get();
        const out = [];
        for (const r of rows) {
            const name = typeof r.name === 'string' ? r.name : '';
            const batch = typeof r.batch === 'number' ? r.batch : Number(r.batch);
            if (name.length === 0)
                continue;
            if (!Number.isFinite(batch))
                continue;
            out.push({ name, batch });
        }
        return out;
    },
    async listAllCompletedNames(db, params) {
        assertDbSupportsMigrations(db);
        const rows = await QueryBuilder.create('migrations', db)
            .select('name')
            .where('status', '=', 'completed')
            .andWhere('scope', '=', params.scope)
            .andWhere('service', '=', toSafeService(params.service))
            .orderBy('batch', 'DESC')
            .orderBy('id', 'DESC')
            .get();
        return rows.map((r) => (typeof r.name === 'string' ? r.name : '')).filter((n) => n.length > 0);
    },
    async deleteRecord(db, params) {
        assertDbSupportsMigrations(db);
        await QueryBuilder.create('migrations', db)
            .where('name', '=', params.name)
            .andWhere('scope', '=', params.scope)
            .andWhere('service', '=', toSafeService(params.service))
            .delete();
    },
});
