/* eslint-disable @typescript-eslint/require-await */
/**
 * MySQL Proxy Adapter (HTTP)
 *
 * Used in Cloudflare Workers when MYSQL_PROXY_URL is configured.
 */
import { Logger } from '../../config/logger.js';
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
import { AdaptersEnum } from '../../migrations/enum/index.js';
import { ensureSignedSettings, isRecord, } from '../adapters/SqlProxyAdapterUtils.js';
import { SqlProxyHttpAdapterShared } from '../adapters/SqlProxyHttpAdapterShared.js';
import { createStatementPayload, getExecMetaWithLastRowId, } from '../adapters/SqlProxyRegistryMode.js';
import { QueryBuilder } from '../QueryBuilder.js';
const buildProxySettings = () => {
    return SqlProxyHttpAdapterShared.buildProxySettingsFromEnv({
        urlKey: 'MYSQL_PROXY_URL',
        keyIdKey: 'MYSQL_PROXY_KEY_ID',
        secretKey: 'MYSQL_PROXY_SECRET',
        timeoutKey: 'MYSQL_PROXY_TIMEOUT_MS',
        sharedKeyIdKey: 'ZT_PROXY_KEY_ID',
        sharedSecretKey: 'ZT_PROXY_SECRET',
        sharedTimeoutKey: 'ZT_PROXY_TIMEOUT_MS',
    });
};
const buildSignedProxyConfig = (settings) => {
    return SqlProxyHttpAdapterShared.buildStandardSignedProxyConfig({
        settings,
        label: 'MySQL',
        urlKey: 'MYSQL_PROXY_URL',
        keyIdKey: 'MYSQL_PROXY_KEY_ID',
        secretKey: 'MYSQL_PROXY_SECRET',
    });
};
const isQueryResponse = (value) => isRecord(value) && Array.isArray(value['rows']) && typeof value['rowCount'] === 'number';
const isQueryOneResponse = (value) => isRecord(value) && 'row' in value;
const requestProxy = async (state, path, payload) => {
    try {
        return await SqlProxyHttpAdapterShared.requestProxy(state.signed, path, payload);
    }
    catch (error) {
        Logger.error('[MySQLProxyAdapter] Proxy request failed', {
            path,
            baseUrl: state.settings.baseUrl,
            timeoutMs: state.settings.timeoutMs,
            hasKeyId: (state.settings.keyId ?? '').trim() !== '',
            hasSecret: (state.settings.secret ?? '').trim() !== '',
            error: error instanceof Error ? error.message : String(error),
        });
        throw error;
    }
};
const resolveProxyMode = () => {
    return SqlProxyHttpAdapterShared.resolveProxyModeFromEnv('MYSQL_PROXY_MODE');
};
const requireConnected = (state) => {
    if (!state.connected)
        throw ErrorFactory.createConnectionError('Database not connected');
};
const toQueryResult = (out) => {
    if (isQueryResponse(out)) {
        return {
            rows: out.rows,
            rowCount: out.rowCount,
            lastInsertId: out.lastInsertId,
        };
    }
    if (isQueryOneResponse(out)) {
        const row = out.row;
        return { rows: row ? [row] : [], rowCount: row ? 1 : 0 };
    }
    const meta = getExecMetaWithLastRowId(out);
    return { rows: [], rowCount: meta.changes, lastInsertId: meta.lastRowId };
};
const createQuery = (state) => async (sql, parameters) => {
    requireConnected(state);
    const mode = resolveProxyMode();
    const out = mode === 'registry'
        ? await requestProxy(state, '/zin/mysql/statement', await createStatementPayload(sql, parameters))
        : await requestProxy(state, '/zin/mysql/query', {
            sql,
            params: parameters,
        });
    return toQueryResult(out);
};
const createQueryOne = (state) => async (sql, parameters) => {
    requireConnected(state);
    const mode = resolveProxyMode();
    if (mode !== 'registry') {
        const out = await requestProxy(state, '/zin/mysql/queryOne', {
            sql,
            params: parameters,
        });
        return out.row ?? null;
    }
    const out = await requestProxy(state, '/zin/mysql/statement', await createStatementPayload(sql, parameters));
    if (isQueryOneResponse(out))
        return out.row ?? null;
    if (isQueryResponse(out))
        return out.rows[0] ?? null;
    return null;
};
const createPing = (queryOne) => async () => {
    await queryOne(QueryBuilder.create('').select('1').toSQL(), []);
};
const createTransaction = (state, getAdapter) => async (callback) => {
    requireConnected(state);
    try {
        return await callback(getAdapter());
    }
    catch (error) {
        throw ErrorFactory.createTryCatchError('MySQL proxy transaction failed', error);
    }
};
const createRawQuery = (state, query) => async (sql, parameters = []) => {
    requireConnected(state);
    const out = await query(sql, parameters);
    return out.rows;
};
const createAdapter = (state) => {
    const query = createQuery(state);
    const queryOne = createQueryOne(state);
    const ping = createPing(queryOne);
    const adapter = {
        async connect() {
            ensureSignedSettings(state.signed);
            state.connected = true;
        },
        async disconnect() {
            state.connected = false;
        },
        query,
        queryOne,
        ping,
        transaction: createTransaction(state, () => adapter),
        rawQuery: createRawQuery(state, query),
        async ensureMigrationsTable() {
            requireConnected(state);
            try {
                await query(`CREATE TABLE IF NOT EXISTS migrations (
            id INTEGER PRIMARY KEY AUTO_INCREMENT,
            name VARCHAR(255) NOT NULL,
            scope VARCHAR(255) NOT NULL DEFAULT 'global',
            service VARCHAR(255) NOT NULL DEFAULT '',
            batch INTEGER NOT NULL,
            status VARCHAR(255) NOT NULL,
            applied_at DATETIME NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            UNIQUE(name, scope, service)
          )`, []);
            }
            catch (error) {
                throw SqlProxyHttpAdapterShared.createProxyNotReachableCliError('MySQL proxy', state.settings.baseUrl, error);
            }
        },
        getType() {
            return AdaptersEnum.mysql;
        },
        isConnected() {
            return state.connected;
        },
        getPlaceholder(_index) {
            return '?';
        },
    };
    return adapter;
};
export const MySQLProxyAdapter = Object.freeze({
    create(_config) {
        const settings = buildProxySettings();
        const signed = buildSignedProxyConfig(settings);
        const state = { connected: false, settings, signed };
        Logger.info('[MySQLProxyAdapter] Created with runtime settings', {
            baseUrl: settings.baseUrl,
            timeoutMs: settings.timeoutMs,
            hasKeyId: (settings.keyId ?? '').trim() !== '',
            hasSecret: (settings.secret ?? '').trim() !== '',
        });
        return createAdapter(state);
    },
});
export default MySQLProxyAdapter;
