/**
 * Catálogo RPC dos Jobs Agendados.
 *
 * O código do job chama `await planfi.call("namespace.acao", params)`. Cada nome aqui
 * mapeia para uma chamada HTTP concreta na API da PlanFi. O host base vem de
 * `SCHEDULED_JOBS_PLANFI_BASE_URL` e a auth é o segredo `PLANFI_KEY` do próprio job
 * (enviado como `X-Planfi-Service-Key` e validado pela matriz de permissões) — ou seja,
 * um job só acessa o que a chave dele tiver permissão.
 *
 * Para adicionar um método: acrescente UMA entrada. `path` aceita `:param`, que é
 * substituído (e consumido) a partir de `params`; os params restantes viram query string
 * (GET) ou corpo JSON (demais verbos). `unwrap` desembrulha um campo da resposta — ex.:
 * `unwrap: "data"` faz `planfi.call("customer.list")` devolver o array direto.
 */
export type RpcHttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';

export interface RpcDescriptor {
  /** Verbo HTTP da chamada. */
  method: RpcHttpMethod;
  /** Caminho relativo ao base; pode conter `:param` (substituído a partir de params). */
  path: string;
  /** Onde os params restantes vão. Default: 'query' para GET, 'body' para os demais. */
  in?: 'query' | 'body';
  /** Campo da resposta JSON a desembrulhar (ex.: 'data' devolve o array direto). */
  unwrap?: string;
}

/**
 * Métodos disponíveis para os jobs. Mantido pequeno e verificado de propósito —
 * cada entrada aponta para um endpoint real do Atlas (CRM interno).
 */
export const PLANFI_RPC_CATALOG: Readonly<Record<string, RpcDescriptor>> = {
  // Clientes (CRM do Atlas). GET /api/clients → { data, page, pageSize, total }.
  // Filtros aceitos viram query: email, name, businessName, businessSubdomain,
  // stripeId, customerApiUuid, hasBusiness, page, pageSize/limit.
  'customer.list': {
    method: 'GET',
    path: '/api/clients',
    in: 'query',
    unwrap: 'data',
  },
  // Envia e-mail pela infra da PlanFi (EmailProvider das automações; em dev cai
  // no Mailtrap). Params { to, subject?, body } vão no corpo do POST.
  'email.send': {
    method: 'POST',
    path: '/api/atlas/jobs/send-email',
    in: 'body',
  },
  // Recalcula a membership dos segmentos dinâmicos do CRM (Atlas). Sem params.
  // Espelha a entrada do TOOL_CATALOG do Atlas (id 'segments.materialize').
  'segments.materialize': {
    method: 'POST',
    path: '/api/atlas/jobs/materialize-segments',
    in: 'body',
  },
  // Sincroniza uso (clientsCount/lastActivityAt) dos clientes do Atlas a partir
  // do BFF. Param { lote? } vai no corpo. Espelha o TOOL_CATALOG do Atlas.
  'customers.syncUsage': {
    method: 'POST',
    path: '/api/atlas/jobs/sync-usage',
    in: 'body',
  },
  // Reconcilia o billing dos clientes do Atlas com o estado ATUAL do Stripe
  // (status/período/cancelamento/última fatura) e recomputa o lifecycleStage.
  // Params { lote?, cursor? } no corpo; iterar até nextCursor voltar null.
  // Espelha o TOOL_CATALOG do Atlas.
  'customers.reconcileBilling': {
    method: 'POST',
    path: '/api/atlas/jobs/reconcile-billing',
    in: 'body',
  },
  // Sincroniza PlanLimit (plano → teto de clientes) no Atlas a partir do core.
  // Sem params. Espelha o TOOL_CATALOG do Atlas.
  'customers.syncPlanLimits': {
    method: 'POST',
    path: '/api/atlas/jobs/sync-plan-limits',
    in: 'body',
  },
};

export interface ResolvedRpc {
  method: RpcHttpMethod;
  /** Caminho com `:param` já substituído. */
  path: string;
  /** Query params (modo 'query'). */
  query?: Record<string, string>;
  /** Corpo (modo 'body'). */
  body?: Record<string, unknown>;
  /** Campo a desembrulhar da resposta. */
  unwrap?: string;
}

/**
 * Resolve um nome RPC + params para uma chamada HTTP concreta.
 * Lança erro claro para método desconhecido ou param de path obrigatório ausente.
 */
export function resolveRpc(
  rpc: string,
  params?: Record<string, unknown> | null,
): ResolvedRpc {
  const descriptor = PLANFI_RPC_CATALOG[rpc];
  if (!descriptor) {
    const known = Object.keys(PLANFI_RPC_CATALOG).join(', ') || '(nenhum)';
    throw new Error(
      `planfi.call: método desconhecido "${rpc}". Métodos disponíveis: ${known}`,
    );
  }

  const rest: Record<string, unknown> =
    params && typeof params === 'object' ? { ...params } : {};

  // Substitui :param no path, consumindo cada um de `rest`.
  const path = descriptor.path.replace(
    /:([A-Za-z0-9_]+)/g,
    (_match, name: string) => {
      const value = rest[name];
      if (value === undefined || value === null || value === '') {
        throw new Error(
          `planfi.call: parâmetro obrigatório "${name}" ausente para "${rpc}"`,
        );
      }
      delete rest[name];
      return encodeURIComponent(String(value));
    },
  );

  const location = descriptor.in ?? (descriptor.method === 'GET' ? 'query' : 'body');

  if (location === 'query') {
    const query: Record<string, string> = {};
    for (const [key, value] of Object.entries(rest)) {
      if (value !== undefined && value !== null) {
        query[key] = String(value);
      }
    }
    return { method: descriptor.method, path, query, unwrap: descriptor.unwrap };
  }

  return { method: descriptor.method, path, body: rest, unwrap: descriptor.unwrap };
}
