import { lerDestinoDaVolta } from "./redirecionamentos.ts";
import type { ProgramaParaRegistrar } from "./registro.ts";

/**
 * A identidade publicada de um programa.
 *
 * Em vez de se registrar a cada conexão, o programa pode se apresentar com um
 * endereço: nesse endereço mora um documento que diz como ele se chama e para
 * onde a pessoa volta. O Claude chama isso de "identidade publicada", e é a
 * opção que a tela dele recomenda.
 *
 * O servidor de permissão precisa ir buscar esse documento, e ir buscar um
 * endereço escolhido por quem chama é um jeito clássico de fazer o servidor
 * bater em porta que não devia. Por isso só são aceitos os documentos de uma
 * lista fechada, a dos programas que conhecemos. Programa fora da lista se
 * registra do jeito antigo, ou se liga pela chave de acesso.
 *
 * Só regra, sem rede e sem banco, para o teste ao lado rodar sozinho.
 */

type DocumentoConhecido = { padrao: RegExp; nome: string };

/* A ordem importa: o endereço do Codex também casaria com o padrão do ChatGPT. */
const DOCUMENTOS: DocumentoConhecido[] = [
  { padrao: /^https:\/\/claude\.ai\/oauth\/mcp-oauth-client-metadata$/, nome: "Claude" },
  { padrao: /^https:\/\/claude\.ai\/oauth\/claude-code-client-metadata$/, nome: "Claude Code" },
  { padrao: /^https:\/\/chatgpt\.com\/oauth\/codex\/(?:[A-Za-z0-9_-]{1,120}\/)?client\.json$/, nome: "Codex" },
  { padrao: /^https:\/\/chatgpt\.com\/oauth\/(?:[A-Za-z0-9_-]{1,120}\/)?client\.json$/, nome: "ChatGPT" },
  { padrao: /^https:\/\/vscode\.dev\/oauth\/client-metadata\.json$/, nome: "VS Code" },
];

const TAMANHO_MAXIMO_DO_ENDERECO = 300;

/** O endereço é o documento de um programa que conhecemos? Devolve o nome dele na nossa lista. */
export function documentoConhecido(endereco: unknown): { nome: string } | null {
  if (typeof endereco !== "string" || endereco.length > TAMANHO_MAXIMO_DO_ENDERECO) return null;
  const conhecido = DOCUMENTOS.find((documento) => documento.padrao.test(endereco));
  return conhecido ? { nome: conhecido.nome } : null;
}

/**
 * Confere o documento que veio do endereço.
 *
 * Duas coisas valem: o documento tem de dizer que é ele mesmo (o campo
 * `client_id` igual ao endereço de onde veio), e os retornos dele passam pela
 * mesma lista fechada de qualquer outro programa. O nome que vale é o da nossa
 * lista, não o que o documento escreve.
 */
export function lerDocumento(endereco: string, corpo: unknown): { ok: true; programa: ProgramaParaRegistrar } | { ok: false; motivo: string } {
  const conhecido = documentoConhecido(endereco);
  if (!conhecido) return { ok: false, motivo: "Programa não reconhecido." };
  if (!corpo || typeof corpo !== "object" || Array.isArray(corpo)) return { ok: false, motivo: "O documento do programa não é um objeto JSON." };
  const dados = corpo as Record<string, unknown>;

  if (dados.client_id !== endereco) return { ok: false, motivo: "O documento do programa não confere com o endereço de onde veio." };

  const voltas = Array.isArray(dados.redirect_uris) ? dados.redirect_uris : [];
  const redirectUris: string[] = [];
  for (const volta of voltas.slice(0, 20)) {
    const destino = lerDestinoDaVolta(volta);
    if (destino && !redirectUris.includes(destino.endereco)) redirectUris.push(destino.endereco);
  }
  if (!redirectUris.length) return { ok: false, motivo: "O documento do programa não traz um endereço de retorno aceito." };

  return {
    ok: true,
    programa: {
      nome: conhecido.nome,
      redirectUris: redirectUris.slice(0, 10),
      /* Identidade publicada é sempre de programa público: quem prova o pedido é o PKCE. */
      metodo: "none",
      conhecido: true,
      dados: typeof dados.client_name === "string" ? { client_name: dados.client_name.slice(0, 80) } : {},
    },
  };
}
