/* eslint-disable @typescript-eslint/require-await */
/**
 * SQL Server Proxy Adapter (HTTP)
 */
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
import { AdaptersEnum } from '../../migrations/enum/index.js';
import { QueryBuilder } from '../QueryBuilder.js';
import { ensureSignedSettings, isRecord, } from '../adapters/SqlProxyAdapterUtils.js';
import { SqlProxyHttpAdapterShared } from '../adapters/SqlProxyHttpAdapterShared.js';
import { createStatementPayload, getExecMetaWithLastRowId, } from '../adapters/SqlProxyRegistryMode.js';
const resolveProxyMode = () => {
    return SqlProxyHttpAdapterShared.resolveProxyModeFromEnv('SQLSERVER_PROXY_MODE');
};
const buildProxySettings = () => {
    return SqlProxyHttpAdapterShared.buildProxySettingsFromEnv({
        urlKey: 'SQLSERVER_PROXY_URL',
        hostKey: 'SQLSERVER_PROXY_HOST',
        portKey: 'SQLSERVER_PROXY_PORT',
        defaultHost: '127.0.0.1',
        defaultPort: 8793,
        keyIdKey: 'SQLSERVER_PROXY_KEY_ID',
        secretKey: 'SQLSERVER_PROXY_SECRET',
        timeoutKey: 'SQLSERVER_PROXY_TIMEOUT_MS',
        sharedTimeoutKey: 'ZT_PROXY_TIMEOUT_MS',
    });
};
const buildSignedProxyConfig = (settings) => {
    return SqlProxyHttpAdapterShared.buildStandardSignedProxyConfig({
        settings,
        label: 'SQL Server',
        urlKey: 'SQLSERVER_PROXY_URL',
        keyIdKey: 'SQLSERVER_PROXY_KEY_ID',
        secretKey: 'SQLSERVER_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 (signed, path, payload) => SqlProxyHttpAdapterShared.requestProxy(signed, path, payload);
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 };
    }
    if (isQueryOneResponse(out)) {
        const row = out.row ?? null;
        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.signed, '/zin/sqlserver/statement', await createStatementPayload(sql, parameters))
        : await requestProxy(state.signed, '/zin/sqlserver/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.signed, '/zin/sqlserver/queryOne', {
            sql,
            params: parameters,
        });
        return out.row ?? null;
    }
    const out = await requestProxy(state.signed, '/zin/sqlserver/statement', await createStatementPayload(sql, parameters));
    if (isQueryOneResponse(out))
        return out.row ?? null;
    if (isQueryResponse(out))
        return out.rows[0] ?? null;
    return null;
};
const createPing = (query) => async () => {
    await query(QueryBuilder.create('').select('1').toSQL(), []);
};
const createTransaction = (state, getAdapter) => async (callback) => {
    if (state.inTransaction) {
        throw ErrorFactory.createGeneralError('Transaction already in progress');
    }
    requireConnected(state);
    state.inTransaction = true;
    try {
        const adapter = getAdapter();
        await adapter.query('BEGIN TRANSACTION', []);
        const result = await callback(adapter);
        await adapter.query('COMMIT', []);
        return result;
    }
    catch (error) {
        try {
            await getAdapter().query('ROLLBACK', []);
        }
        catch {
            void 0;
        }
        throw error;
    }
    finally {
        state.inTransaction = false;
    }
};
const createRawQuery = (query) => async (sql, parameters) => {
    const result = await query(sql, parameters ?? []);
    return result.rows;
};
const createAdapter = (state) => {
    const query = createQuery(state);
    const queryOne = createQueryOne(state);
    const adapter = {
        async connect() {
            ensureSignedSettings(state.signed);
            state.connected = true;
        },
        async disconnect() {
            state.connected = false;
            state.inTransaction = false;
        },
        query,
        queryOne,
        ping: createPing(query),
        transaction: createTransaction(state, () => adapter),
        rawQuery: createRawQuery(query),
        async ensureMigrationsTable() {
            requireConnected(state);
            try {
                await query(`IF OBJECT_ID(N'migrations', N'U') IS NULL
BEGIN
  CREATE TABLE migrations (
    id INT IDENTITY(1,1) PRIMARY KEY,
    name NVARCHAR(255) NOT NULL,
    scope NVARCHAR(255) NOT NULL DEFAULT 'global',
    service NVARCHAR(255) NOT NULL DEFAULT '',
    batch INT NOT NULL,
    status NVARCHAR(255) NOT NULL,
    applied_at DATETIME2 NULL,
    created_at DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),
    CONSTRAINT UQ_migrations_name_scope_service UNIQUE (name, scope, service)
  );
END`, []);
            }
            catch (error) {
                throw SqlProxyHttpAdapterShared.createProxyNotReachableCliError('SQL Server proxy', state.settings.baseUrl, error);
            }
        },
        getType() {
            return AdaptersEnum.sqlserver;
        },
        isConnected() {
            return state.connected;
        },
        getPlaceholder(index) {
            return `@param${index}`;
        },
    };
    return adapter;
};
export function createSqlServerProxyAdapter() {
    const settings = buildProxySettings();
    const signed = buildSignedProxyConfig(settings);
    const state = { connected: false, inTransaction: false, settings, signed };
    return createAdapter(state);
}
