import { readerPool, writerPool } from '../db/pool.js';
import type { RpcRequest, RpcResponse } from './types.js';

type SavedRow = {
  schema_name: string;
  proc_name: string;
  opertype: number;
};

// Маленький кэш реестра (читается из core.saved). Инвалидация — при изменении core.saved
// через qmanager/invalidator (когда добавим).
const registry = new Map<string, SavedRow>();

async function lookup(name: string): Promise<SavedRow | null> {
  const cached = registry.get(name);
  if (cached) return cached;
  const { rows } = await readerPool.query<SavedRow>(
    'SELECT schema_name, proc_name, opertype FROM core.saved WHERE name = $1',
    [name],
  );
  const row = rows[0];
  if (row) registry.set(name, row);
  return row ?? null;
}

export function invalidateRegistry(name?: string): void {
  if (name) registry.delete(name); else registry.clear();
}

export async function dispatch(req: RpcRequest, actor: string): Promise<RpcResponse> {
  const saved = await lookup(req.name);
  if (!saved) {
    return { id: req.id, ok: false, error: { code: 'NOT_FOUND', message: `procedure '${req.name}' not registered` } };
  }

  const pool = saved.opertype === 0 ? readerPool : writerPool;
  const client = await pool.connect();
  try {
    // SET LOCAL действует только внутри текущей транзакции — на reader без транзакции
    // используем SET SESSION-эквивалент через set_config(..., false).
    await client.query(`SELECT set_config('mocl.actor', $1, false)`, [actor]);

    // CALL ... USING $1::jsonb. Процедура принимает один JSONB-параметр и возвращает один JSONB out.
    const sql = `CALL ${escapeIdent(saved.schema_name)}.${escapeIdent(saved.proc_name)}($1::jsonb, NULL)`;
    const params = [req.params ?? {}];
    const { rows } = await client.query(sql, params);
    const out = rows[0] ?? null;
    return { id: req.id, ok: true, result: out };
  } catch (err) {
    const e = err as Error & { code?: string };
    return { id: req.id, ok: false, error: { code: e.code ?? 'PG_ERROR', message: e.message } };
  } finally {
    client.release();
  }
}

function escapeIdent(s: string): string {
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(s)) throw new Error(`invalid identifier: ${s}`);
  return `"${s}"`;
}
