import { ESCOPOS } from "../chave.ts";
import { METODOS } from "./registro.ts";

/**
 * Os dois cartões de visita que o programa lê antes de começar a conexão.
 *
 * O primeiro é do conector ("sou protegido, e quem dá acesso a mim é este
 * servidor de permissão"). O segundo é do servidor de permissão ("a tela de
 * permitir fica aqui, a troca do código fica ali, o registro acolá"). O
 * programa descobre tudo sozinho a partir do endereço do conector; ninguém
 * digita nada disso.
 *
 * Só regra, sem banco, para o teste ao lado rodar sozinho.
 */

export type Enderecos = {
  /* O endereço que a pessoa cola no assistente. Em produção, https://mcp.ktalogu.com.br */
  conector: string;
  /* A origem em que moram o registro e a troca de código (a mesma do conector). */
  emissor: string;
  /* A origem do painel, onde a pessoa está logada e onde fica a tela de permitir. */
  painel: string;
  /* A origem pela qual ESTE pedido chegou (a do conector ou a do painel). */
  pedida: string;
};

const semBarra = (valor: string) => valor.replace(/\/+$/, "");

/**
 * Monta os endereços a partir de onde o pedido chegou.
 *
 * O endereço do conector que a pessoa cola no assistente sempre tem caminho
 * (`/mcp` em produção). Com o endereço seco, cada programa decide de um jeito
 * se põe ou não uma barra no fim, e os mais rigorosos recusam a conexão quando
 * a barra deles não bate com a nossa. Com caminho, não há o que decidir.
 */
export function montarEnderecos(origem: { pedida: string; painel: string; doAmbiente?: string | null }): Enderecos {
  const painel = semBarra(origem.painel).replace(/\/\/mcp\./, "//admin.");

  let conector = "";
  const doAmbiente = semBarra(String(origem.doAmbiente || "").trim());
  if (doAmbiente) {
    try {
      conector = new URL(doAmbiente).pathname === "/" ? `${doAmbiente}/mcp` : doAmbiente;
    } catch {
      conector = "";
    }
  }
  if (!conector) {
    /* Em produção o conector é o subdomínio `mcp.` do painel. Fora dela (na
       máquina de quem desenvolve) é a própria rota, no endereço do painel. */
    conector = /\/\/admin\./.test(painel) ? `${painel.replace(/\/\/admin\./, "//mcp.")}/mcp` : `${painel}/api/mcp`;
  }

  return { conector, emissor: new URL(conector).origin, painel, pedida: semBarra(origem.pedida) };
}

/**
 * Onde o programa acha o primeiro cartão. A norma manda o caminho do conector
 * entrar no fim do endereço; `caminho` é o caminho que o programa usou para
 * chegar ao conector ("" para a raiz).
 */
export function enderecoDosMetadadosDoRecurso(enderecos: Enderecos, caminho: string) {
  return `${semBarra(enderecos.pedida)}/.well-known/oauth-protected-resource${caminho}`;
}

/**
 * O primeiro cartão. O campo `resource` tem de ser idêntico ao endereço que o
 * programa usou para falar com o conector; programa rigoroso descarta o cartão
 * quando não é.
 */
export function metadadosDoRecurso(enderecos: Enderecos, caminho: string) {
  return {
    resource: `${semBarra(enderecos.pedida)}${caminho}`,
    authorization_servers: [semBarra(enderecos.emissor)],
    scopes_supported: [...ESCOPOS],
    bearer_methods_supported: ["header"],
    resource_name: "Ktalogu",
  };
}

export function metadadosDoServidor(enderecos: Enderecos) {
  const emissor = semBarra(enderecos.emissor);
  return {
    issuer: emissor,
    authorization_endpoint: `${semBarra(enderecos.painel)}/oauth/authorize`,
    token_endpoint: `${emissor}/oauth/token`,
    registration_endpoint: `${emissor}/oauth/register`,
    revocation_endpoint: `${emissor}/oauth/revoke`,
    response_types_supported: ["code"],
    response_modes_supported: ["query"],
    grant_types_supported: ["authorization_code", "refresh_token"],
    code_challenge_methods_supported: ["S256"],
    token_endpoint_auth_methods_supported: [...METODOS],
    revocation_endpoint_auth_methods_supported: [...METODOS],
    scopes_supported: [...ESCOPOS],
    authorization_response_iss_parameter_supported: true,
    /* A identidade publicada do programa é aceita no lugar do registro (só as da lista fechada). */
    client_id_metadata_document_supported: true,
  };
}

/** As origens para as quais um acesso pode ser pedido: o conector e, por tolerância, o painel (que também atende a rota). */
export function origensDoConector(enderecos: Enderecos): string[] {
  return [...new Set([new URL(enderecos.conector).origin, new URL(enderecos.painel).origin])];
}

/* Os caminhos em que o conector atende: a raiz e `/mcp` no domínio dele, e a
   rota em si. O programa pergunta pelo cartão de visita do caminho que usou. */
const CAMINHOS_DO_CONECTOR = ["", "/mcp", "/api/mcp"] as const;

/** O caminho pedido depois de `/.well-known/oauth-protected-resource`, se for um dos nossos. */
export function caminhoDoConector(sufixo: string): string | null {
  const limpo = sufixo.replace(/\/+$/, "");
  return (CAMINHOS_DO_CONECTOR as readonly string[]).includes(limpo) ? limpo : null;
}

/** O caminho pelo qual o programa chegou ao conector (o `proxy.ts` anota no cabeçalho). */
export function caminhoUsado(cabecalhos: { get(nome: string): string | null }): string {
  const anotado = (cabecalhos.get("x-ktalogu-caminho-do-conector") || "").trim();
  if (anotado === "/") return "";
  return anotado === "/mcp" ? "/mcp" : "/api/mcp";
}
