/* ============================================================
 * Copyright (c) 2026 RAVAPI Soluções. www.ravapi.com
 * Acesso ao banco com tenant obrigatório
 *
 * Toda transação começa definindo app.tenant_id. Sem isso as políticas
 * de RLS não casam com nada e a consulta volta vazia — falha fechada,
 * que é o comportamento seguro, mas silenciosa. Por isso a checagem é
 * explícita aqui e estoura antes de tocar no banco.
 * ============================================================ */

import { AsyncLocalStorage } from 'node:async_hooks';
import { TenantAusente } from '../core/errors.ts';

export interface Contexto { tenantId: string; }

const armazenamento = new AsyncLocalStorage<Contexto>();

export function comContexto<T>(ctx: Contexto, fn: () => T): T {
  if (!ctx.tenantId) throw new TenantAusente();
  return armazenamento.run(ctx, fn);
}

export function tenantAtual(): string {
  const ctx = armazenamento.getStore();
  if (!ctx?.tenantId) throw new TenantAusente();
  return ctx.tenantId;
}

/** Mínimo que o módulo precisa de um pool — mantém `pg` fora do domínio. */
export interface Executor {
  query<R = any>(texto: string, valores?: unknown[]): Promise<{ rows: R[]; rowCount: number | null }>;
}

export interface Pool {
  connect(): Promise<Executor & { release(): void }>;
}

/**
 * Roda o callback numa transação com app.tenant_id já definido.
 *
 * set_config com is_local = true amarra o valor à transação: ao devolver
 * a conexão ao pool, o tenant não vaza para a próxima requisição. Com o
 * pooler do Neon isso não é detalhe — é o que separa dois clientes.
 */
export async function emTransacao<T>(pool: Pool, fn: (x: Executor) => Promise<T>): Promise<T> {
  const tenant = tenantAtual();
  const conn = await pool.connect();
  try {
    await conn.query('BEGIN');
    await conn.query('SELECT set_config($1, $2, true)', ['app.tenant_id', tenant]);
    const saida = await fn(conn);
    await conn.query('COMMIT');
    return saida;
  } catch (erro) {
    await conn.query('ROLLBACK').catch(() => {});
    throw erro;
  } finally {
    conn.release();
  }
}
