/* eslint-disable @typescript-eslint/require-await */
/**
 * PostgreSQL Proxy Adapter (HTTP)
 *
 * Used in Cloudflare Workers when POSTGRES_PROXY_URL is configured.
 */
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
import { AdaptersEnum } from '../../migrations/enum/index.js';
import { isRecord, } from '../adapters/SqlProxyAdapterUtils.js';
import { SqlProxyHttpAdapterShared } from '../adapters/SqlProxyHttpAdapterShared.js';
import { createStatementPayload } from '../adapters/SqlProxyRegistryMode.js';
import { QueryBuilder } from '../QueryBuilder.js';
const buildProxySettings = () => {
    return SqlProxyHttpAdapterShared.buildProxySettingsFromEnv({
        urlKey: 'POSTGRES_PROXY_URL',
        hostKey: 'POSTGRES_PROXY_HOST',
        portKey: 'POSTGRES_PROXY_PORT',
        defaultHost: '127.0.0.1',
        defaultPort: 8790,
        keyIdKey: 'POSTGRES_PROXY_KEY_ID',
        secretKey: 'POSTGRES_PROXY_SECRET',
        timeoutKey: 'POSTGRES_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: 'PostgreSQL',
        urlKey: 'POSTGRES_PROXY_URL',
        keyIdKey: 'POSTGRES_PROXY_KEY_ID',
        secretKey: 'POSTGRES_PROXY_SECRET',
    });
};
const isQueryResponse = (value) => isRecord(value) && Array.isArray(value['rows']) && typeof value['rowCount'] === 'number';
const isQueryOneResponse = (value) => isRecord(value) && 'row' in value;
const normalizeLastInsertId = (value) => {
    if (typeof value === 'number' || typeof value === 'string' || typeof value === 'bigint') {
        return value;
    }
    return undefined;
};
const extractRowId = (row) => {
    if (!isRecord(row))
        return undefined;
    return normalizeLastInsertId(row['id']);
};
const getExecMeta = (value) => {
    if (!isRecord(value) || typeof value['ok'] !== 'boolean')
        return { changes: 0 };
    const meta = value['meta'];
    if (!isRecord(meta))
        return { changes: 0 };
    const changes = typeof meta['changes'] === 'number' ? meta['changes'] : 0;
    return { changes };
};
const toQueryResult = (out) => {
    if (isQueryResponse(out)) {
        return {
            rows: out.rows,
            rowCount: out.rowCount,
            lastInsertId: extractRowId(out.rows[0]),
        };
    }
    if (isQueryOneResponse(out)) {
        const row = out.row ?? null;
        return {
            rows: row ? [row] : [],
            rowCount: row ? 1 : 0,
            lastInsertId: extractRowId(row),
        };
    }
    const meta = getExecMeta(out);
    return { rows: [], rowCount: meta.changes };
};
const resolveProxyMode = () => {
    return SqlProxyHttpAdapterShared.resolveProxyModeFromEnv('POSTGRES_PROXY_MODE');
};
const requireConnected = (state) => {
    if (!state.connected)
        throw ErrorFactory.createConnectionError('Database not connected');
};
const createQuery = (state) => async (sql, parameters) => {
    requireConnected(state);
    const mode = resolveProxyMode();
    const out = mode === 'registry'
        ? await SqlProxyHttpAdapterShared.requestProxy(state.signed, '/zin/postgres/statement', await createStatementPayload(sql, parameters))
        : await SqlProxyHttpAdapterShared.requestProxy(state.signed, '/zin/postgres/query', {
            sql,
            params: parameters,
        });
    return toQueryResult(out);
};
const createQueryOne = (state) => async (sql, parameters) => {
    requireConnected(state);
    const mode = resolveProxyMode();
    if (mode !== 'registry') {
        const out = await SqlProxyHttpAdapterShared.requestProxy(state.signed, '/zin/postgres/queryOne', {
            sql,
            params: parameters,
        });
        return out.row ?? null;
    }
    const out = await SqlProxyHttpAdapterShared.requestProxy(state.signed, '/zin/postgres/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('PostgreSQL proxy transaction failed', error);
    }
};
const createRawQuery = (state, query) => async (sql, parameters = []) => {
    requireConnected(state);
    const out = await query(sql, parameters);
    return out.rows;
};
const createEnsureMigrationsTable = (state, query) => async () => {
    requireConnected(state);
    try {
        await query(`CREATE TABLE IF NOT EXISTS migrations (
            id SERIAL PRIMARY KEY,
            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 TIMESTAMP NULL,
            created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
            UNIQUE(name, scope, service)
          )`, []);
    }
    catch (error) {
        throw SqlProxyHttpAdapterShared.createProxyNotReachableCliError('PostgreSQL proxy', state.settings.baseUrl, error);
    }
};
const createAdapter = (state) => {
    const query = createQuery(state);
    const queryOne = createQueryOne(state);
    const adapter = {
        async connect() {
            SqlProxyHttpAdapterShared.ensureSignedProxyConfig(state.signed);
            state.connected = true;
        },
        async disconnect() {
            state.connected = false;
        },
        query,
        queryOne,
        ping: createPing(queryOne),
        transaction: createTransaction(state, () => adapter),
        rawQuery: createRawQuery(state, query),
        ensureMigrationsTable: createEnsureMigrationsTable(state, query),
        getType() {
            return AdaptersEnum.postgresql;
        },
        isConnected() {
            return state.connected;
        },
        getPlaceholder(index) {
            return `$${index}`;
        },
    };
    return adapter;
};
export const PostgreSQLProxyAdapter = Object.freeze({
    create(_config) {
        const settings = buildProxySettings();
        const signed = buildSignedProxyConfig(settings);
        const state = { connected: false, settings, signed };
        return createAdapter(state);
    },
});
export default PostgreSQLProxyAdapter;
