import type { SupabaseClient } from "@supabase/supabase-js";

import { hashDaChave, lerEscopos, pareceChave, temMarca, type Escopo } from "../chave";
import { segredoDoServidor } from "../segredo";
import { lerDocumento } from "./documento";
import {
  ACESSO_VALE_POR_MS,
  acessoDaRenovacao,
  CODIGO_VALE_POR_MS,
  codigoComMarca,
  CONEXAO_PARADA_VALE_POR_MS,
  FOLGA_DA_RENOVACAO_MS,
  mesmoResumo,
  novoCodigo,
  novoSegredoDoPrograma,
  pareceRenovacao,
  primeiraRenovacao,
  renovacaoSeguinte,
  REPOUSO_DA_RENOVACAO_MS,
  resumoDaFicha,
} from "./fichas";
import type { IdentidadeDoPrograma } from "./pedido";
import { pkceConfere } from "./pkce";
import { lerDestinoDaVolta } from "./redirecionamentos";
import type { MetodoDoPrograma, ProgramaParaRegistrar } from "./registro";

/**
 * O servidor de permissão: registrar programa, emitir código, trocar código
 * por acesso, renovar e desligar.
 *
 * Tudo aqui usa a chave de serviço, porque as tabelas do login por botão são
 * fechadas para o navegador. Quem chama estas funções já conferiu o que
 * precisava (a sessão da pessoa na tela de permitir, o programa na troca).
 */

/** O segredo que marca e deriva os códigos, os acessos e as renovações. */
export function segredoDasFichas() {
  return segredoDoServidor("ktalogu-assistentes-fichas");
}

export type ProgramaRegistrado = {
  /* O número do programa aqui dentro. */
  id: string;
  /* O endereço da identidade publicada, para os programas que se apresentam por ela. */
  documento: string | null;
  nome: string;
  redirectUris: string[];
  metodo: MetodoDoPrograma;
  segredoHash: string | null;
  criadoEm: string;
};

type LinhaDoPrograma = {
  id: string;
  documento: string | null;
  nome: string;
  redirect_uris: string[] | null;
  metodo: string;
  segredo_hash: string | null;
  criado_em: string;
};

const COLUNAS_DO_PROGRAMA = "id,documento,nome,redirect_uris,metodo,segredo_hash,criado_em";

function programaDaLinha(linha: LinhaDoPrograma): ProgramaRegistrado {
  return {
    id: linha.id,
    documento: linha.documento,
    nome: linha.nome,
    redirectUris: linha.redirect_uris ?? [],
    metodo: (linha.metodo as MetodoDoPrograma) ?? "none",
    segredoHash: linha.segredo_hash,
    criadoEm: linha.criado_em,
  };
}

/** Como o programa se chama para fora: o endereço da identidade publicada, ou o número do registro. */
export function identificadorPublico(programa: ProgramaRegistrado) {
  return programa.documento ?? programa.id;
}

/* Programa que se registrou e não ligou ninguém em seis horas é lixo: quem se
   registra para valer termina a conexão em minutos. A faxina pega carona em
   todo registro novo, e é ela que segura o tamanho da tabela: o registro é uma
   porta aberta, e um teto fixo de registros seria um jeito de qualquer um
   fechá-la para os outros. */
const PROGRAMA_SEM_USO_VALE_POR_MS = 6 * 60 * 60 * 1000;
const CHANCE_DE_FAXINA = 0.05;

export async function registrarPrograma(
  admin: SupabaseClient,
  programa: ProgramaParaRegistrar,
  de: string | null
): Promise<{ programa: ProgramaRegistrado; segredo: string | null } | null> {
  const segredo = programa.metodo === "none" ? null : novoSegredoDoPrograma();
  const { data, error } = await admin
    .from("clientes_oauth")
    .insert({
      nome: programa.nome,
      redirect_uris: programa.redirectUris,
      metodo: programa.metodo,
      segredo_hash: segredo?.hash ?? null,
      dados: programa.dados,
      registrado_de: de,
    })
    .select(COLUNAS_DO_PROGRAMA)
    .single();
  if (error || !data) {
    console.error("[oauth] não foi possível registrar o programa:", error?.message);
    return null;
  }

  const { error: erroDaFaxina } = await admin
    .from("clientes_oauth")
    .delete()
    .is("ultimo_uso_em", null)
    .is("documento", null)
    .lt("criado_em", new Date(Date.now() - PROGRAMA_SEM_USO_VALE_POR_MS).toISOString());
  if (erroDaFaxina) console.error("[oauth] não foi possível limpar os programas sem uso:", erroDaFaxina.message);

  return { programa: programaDaLinha(data as LinhaDoPrograma), segredo: segredo?.valor ?? null };
}

/* O documento de uma identidade publicada muda raramente. Uma hora de lembrança
   poupa uma ida à internet a cada tela de permissão; a falha é lembrada por um
   minuto, para um endereço fora do ar não ser martelado. */
const DOCUMENTO_VALE_POR_MS = 60 * 60 * 1000;
const FALHA_VALE_POR_MS = 60 * 1000;
const TAMANHO_MAXIMO_DO_DOCUMENTO = 64 * 1024;

declare global {
  var __documentosDosProgramas: Map<string, { programa: ProgramaRegistrado | null; ate: number }> | undefined;
}

function documentosLembrados() {
  globalThis.__documentosDosProgramas ??= new Map();
  return globalThis.__documentosDosProgramas;
}

/** Vai buscar o documento. Só é chamada para endereços da lista fechada (`documentoConhecido`). */
async function baixarDocumento(endereco: string): Promise<unknown | null> {
  try {
    const resposta = await fetch(endereco, {
      /* Redirecionamento não é seguido: o endereço conferido é o que vale. */
      redirect: "error",
      cache: "no-store",
      headers: { Accept: "application/json" },
      signal: AbortSignal.timeout(5000),
    });
    if (!resposta.ok || !resposta.body) return null;
    /* Lido aos pedaços, parando no limite: o tamanho só se sabe lendo. */
    const leitor = resposta.body.getReader();
    const pedacos: Uint8Array[] = [];
    let lidos = 0;
    for (;;) {
      const { done, value } = await leitor.read();
      if (done) break;
      lidos += value.byteLength;
      if (lidos > TAMANHO_MAXIMO_DO_DOCUMENTO) {
        await leitor.cancel().catch(() => undefined);
        return null;
      }
      pedacos.push(value);
    }
    return JSON.parse(Buffer.concat(pedacos).toString("utf8"));
  } catch (falha) {
    console.error("[oauth] não foi possível buscar a identidade publicada:", endereco, falha instanceof Error ? falha.message : falha);
    return null;
  }
}

async function programaDoDocumento(admin: SupabaseClient, endereco: string, buscar: boolean): Promise<ProgramaRegistrado | null> {
  const guardado = async () => {
    const { data, error } = await admin.from("clientes_oauth").select(COLUNAS_DO_PROGRAMA).eq("documento", endereco).maybeSingle();
    if (error) console.error("[oauth] não foi possível ler o programa:", error.message);
    return data ? programaDaLinha(data as LinhaDoPrograma) : null;
  };
  if (!buscar) return guardado();

  const lembrado = documentosLembrados().get(endereco);
  if (lembrado && lembrado.ate > Date.now()) return lembrado.programa ?? guardado();

  const lembrar = (programa: ProgramaRegistrado | null) => {
    if (documentosLembrados().size > 500) documentosLembrados().clear();
    documentosLembrados().set(endereco, { programa, ate: Date.now() + (programa ? DOCUMENTO_VALE_POR_MS : FALHA_VALE_POR_MS) });
    return programa;
  };

  const corpo = await baixarDocumento(endereco);
  const lido = corpo === null ? null : lerDocumento(endereco, corpo);
  if (!lido || !lido.ok) {
    if (lido && !lido.ok) console.error("[oauth] identidade publicada recusada:", endereco, lido.motivo);
    /* Não deu para ler agora. Se o programa já passou por aqui, vale o que ficou guardado. */
    lembrar(null);
    return guardado();
  }

  const { data, error } = await admin
    .from("clientes_oauth")
    .upsert(
      { documento: endereco, nome: lido.programa.nome, redirect_uris: lido.programa.redirectUris, metodo: "none", segredo_hash: null, dados: lido.programa.dados },
      { onConflict: "documento" }
    )
    .select(COLUNAS_DO_PROGRAMA)
    .single();
  if (error || !data) {
    console.error("[oauth] não foi possível guardar a identidade publicada:", error?.message);
    return guardado();
  }
  return lembrar(programaDaLinha(data as LinhaDoPrograma));
}

/**
 * Acha o programa pela identidade que ele apresentou.
 *
 * `buscarDocumento` só é ligado na tela de permissão: é ali que o documento da
 * identidade publicada é lido (ou relido). Na troca do código e na renovação
 * vale o que ficou guardado, sem ir à internet.
 */
export async function buscarPrograma(
  admin: SupabaseClient,
  identidade: IdentidadeDoPrograma,
  opcoes: { buscarDocumento?: boolean } = {}
): Promise<ProgramaRegistrado | null> {
  if (identidade.tipo === "documento") return programaDoDocumento(admin, identidade.endereco, opcoes.buscarDocumento === true);

  const { data, error } = await admin.from("clientes_oauth").select(COLUNAS_DO_PROGRAMA).eq("id", identidade.id).is("documento", null).maybeSingle();
  if (error) {
    console.error("[oauth] não foi possível ler o programa:", error.message);
    return null;
  }
  return data ? programaDaLinha(data as LinhaDoPrograma) : null;
}

/**
 * O programa prova que é ele. Programa público (a maioria) não tem segredo, e
 * a prova dele é o PKCE, conferido na troca. Programa que pediu segredo no
 * registro tem de mostrá-lo.
 */
export function programaConfere(programa: ProgramaRegistrado, segredoRecebido: string | null): boolean {
  if (programa.metodo === "none") return true;
  if (!segredoRecebido || !programa.segredoHash) return false;
  return mesmoResumo(resumoDaFicha(segredoRecebido), programa.segredoHash);
}

/** A pessoa clicou em "Permitir": nasce o código que volta para o programa. */
export async function criarCodigo(
  admin: SupabaseClient,
  dados: {
    programaId: string;
    usuario: { id: string; email: string | null };
    companyId: string;
    donoId: string | null;
    escopos: Escopo[];
    redirectUri: string;
    desafio: string;
    recurso: string | null;
  }
): Promise<string | null> {
  const codigo = novoCodigo(segredoDasFichas());
  const { error } = await admin.from("autorizacoes_oauth").insert({
    codigo_hash: codigo.hash,
    cliente_id: dados.programaId,
    usuario_id: dados.usuario.id,
    usuario_email: dados.usuario.email,
    company_id: dados.companyId,
    dono_id: dados.donoId,
    escopos: dados.escopos,
    redirect_uri: dados.redirectUri,
    desafio: dados.desafio,
    recurso: dados.recurso,
    expira_em: new Date(Date.now() + CODIGO_VALE_POR_MS).toISOString(),
  });
  if (error) {
    console.error("[oauth] não foi possível criar o código:", error.message);
    return null;
  }

  /* Código velho não serve para nada. Fica um dia, para o caso de alguém
     precisar entender uma troca que deu errado, e some. */
  if (Math.random() < CHANCE_DE_FAXINA) {
    const { error: erroDaFaxina } = await admin
      .from("autorizacoes_oauth")
      .delete()
      .lt("expira_em", new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString());
    if (erroDaFaxina) console.error("[oauth] não foi possível limpar os códigos velhos:", erroDaFaxina.message);
  }
  return codigo.valor;
}

export type RespostaDeAcesso = {
  access_token: string;
  token_type: "Bearer";
  expires_in: number;
  refresh_token: string;
  scope: string;
};

export type RecusaDeAcesso = {
  erro: "invalid_grant" | "invalid_request" | "server_error";
  descricao: string;
};

export type Desfecho = { ok: true; resposta: RespostaDeAcesso } | ({ ok: false } & RecusaDeAcesso);

const recusa = (erro: RecusaDeAcesso["erro"], descricao: string): Desfecho => ({ ok: false, erro, descricao });

function respostaDeAcesso(acesso: string, renovacao: string, escopos: unknown, valePorMs: number): Desfecho {
  return {
    ok: true,
    resposta: {
      access_token: acesso,
      token_type: "Bearer",
      expires_in: Math.max(1, Math.floor(valePorMs / 1000)),
      refresh_token: renovacao,
      scope: (lerEscopos(escopos) ?? ["leitura"]).join(" "),
    },
  };
}

/* Quantas conexões vivas uma pessoa pode ter numa empresa. Passou disso, as
   mais antigas são desligadas: programa instalado de novo se registra de novo,
   e sem teto a lista cresceria para sempre. */
export const MAXIMO_DE_CONEXOES = 20;

type CodigoUsado = {
  id: string;
  cliente_id: string;
  usuario_id: string;
  usuario_email: string | null;
  company_id: string;
  dono_id: string | null;
  escopos: string[];
  redirect_uri: string;
  desafio: string;
};

/** O começo de toda instrução que desliga conexões: só as que nasceram do botão e ainda valem. */
function desligarConexoes(admin: SupabaseClient) {
  return admin.from("chaves_de_acesso").update({ revogada_em: new Date().toISOString() }).eq("origem", "conexao").is("revogada_em", null);
}

/** Troca o código pelo primeiro acesso. */
export async function trocarCodigo(
  admin: SupabaseClient,
  pedido: { codigo: unknown; verificador: unknown; redirectUri: string | null; programa: ProgramaRegistrado }
): Promise<Desfecho> {
  const segredo = segredoDasFichas();
  if (!codigoComMarca(segredo, pedido.codigo)) return recusa("invalid_grant", "Código inválido.");

  const { data, error } = await admin.rpc("usar_codigo_de_autorizacao", { p_hash: resumoDaFicha(pedido.codigo) });
  if (error) {
    console.error("[oauth] não foi possível usar o código:", error.message);
    return recusa("server_error", "Não foi possível conferir o código agora.");
  }
  const uso = (data ?? {}) as { situacao?: string; codigo?: CodigoUsado; autorizacao_id?: string; cliente_id?: string; usada_em?: string };

  if (uso.situacao === "repetido" && uso.autorizacao_id) {
    /* O mesmo código apareceu duas vezes, pelo mesmo programa, logo depois da
       primeira troca: um dos dois pedidos não é do programa de verdade, e não
       há como saber qual. A conexão que nasceu deste código é desfeita.
       Código velho repetido por outro programa, ou muito depois, não derruba
       nada: seria um jeito de qualquer um com um código antigo na mão
       desligar a conexão de outra pessoa. */
    const usadaEm = uso.usada_em ? new Date(uso.usada_em).getTime() : 0;
    if (uso.cliente_id === pedido.programa.id && Date.now() - usadaEm <= CODIGO_VALE_POR_MS * 3) {
      const { error: erroAoDesligar } = await desligarConexoes(admin).eq("autorizacao_id", uso.autorizacao_id);
      if (erroAoDesligar) console.error("[oauth] não foi possível desfazer a conexão do código repetido:", erroAoDesligar.message);
    }
    return recusa("invalid_grant", "Este código já foi usado.");
  }
  if (uso.situacao !== "ok" || !uso.codigo) return recusa("invalid_grant", "Código inválido ou vencido.");

  const codigo = uso.codigo;
  if (codigo.cliente_id !== pedido.programa.id) return recusa("invalid_grant", "Este código foi emitido para outro programa.");
  if (pedido.redirectUri !== null && pedido.redirectUri !== codigo.redirect_uri) {
    return recusa("invalid_grant", "O endereço de retorno não é o mesmo do pedido de permissão.");
  }
  if (!pkceConfere(pedido.verificador, codigo.desafio)) return recusa("invalid_grant", "code_verifier não confere.");

  const escopos = lerEscopos(codigo.escopos) ?? ["leitura"];
  const renovacao = primeiraRenovacao(segredo);
  const acesso = acessoDaRenovacao(segredo, renovacao.valor);
  const agora = Date.now();

  /* Ligar de novo o mesmo programa, na mesma empresa, troca a conexão antiga
     pela nova em vez de empilhar. Vale para o programa que se registrou aqui
     (o registro é de uma instalação) e para o assistente de site (o Claude e
     o ChatGPT do navegador): quem clica em Conectar de novo ali é porque a
     conexão anterior não serve mais. Fica de fora o programa de computador
     que se apresenta pela identidade publicada (o Claude Code): a mesma
     identidade vale para todos os computadores da pessoa, e ligar o segundo
     não pode desligar o primeiro. */
  const destino = lerDestinoDaVolta(codigo.redirect_uri);
  if (!pedido.programa.documento || destino?.tipo === "site") {
    const { error: erroAoTrocar } = await desligarConexoes(admin)
      .eq("usuario_id", codigo.usuario_id)
      .eq("company_id", codigo.company_id)
      .eq("cliente_oauth_id", pedido.programa.id);
    if (erroAoTrocar) console.error("[oauth] não foi possível desligar a conexão anterior:", erroAoTrocar.message);
  }

  /* O nome da conexão na lista do painel. Quando o retorno é de um assistente
     conhecido, vale o nome da nossa lista; senão, o que o programa informou. */
  const nome = (destino?.assistente ?? pedido.programa.nome).slice(0, 80) || "Programa sem nome";

  const { error: erroAoCriar } = await admin.from("chaves_de_acesso").insert({
    company_id: codigo.company_id,
    dono_id: codigo.dono_id,
    usuario_id: codigo.usuario_id,
    usuario_email: codigo.usuario_email,
    nome,
    prefixo: acesso.prefixo,
    hash: acesso.hash,
    escopos,
    origem: "conexao",
    cliente_oauth_id: pedido.programa.id,
    autorizacao_id: codigo.id,
    acesso_expira_em: new Date(agora + ACESSO_VALE_POR_MS).toISOString(),
    renovacao_hash: renovacao.hash,
    renovacao_expira_em: new Date(agora + CONEXAO_PARADA_VALE_POR_MS).toISOString(),
  });
  if (erroAoCriar) {
    console.error("[oauth] não foi possível criar a conexão:", erroAoCriar.message);
    return recusa("server_error", "Não foi possível criar a conexão agora.");
  }

  const { error: erroAoMarcar } = await admin.from("clientes_oauth").update({ ultimo_uso_em: new Date(agora).toISOString() }).eq("id", pedido.programa.id);
  if (erroAoMarcar) console.error("[oauth] não foi possível marcar o uso do programa:", erroAoMarcar.message);
  await apararConexoes(admin, codigo.usuario_id, codigo.company_id);

  return respostaDeAcesso(acesso.valor, renovacao.valor, escopos, ACESSO_VALE_POR_MS);
}

async function apararConexoes(admin: SupabaseClient, usuarioId: string, companyId: string) {
  const { data, error } = await admin
    .from("chaves_de_acesso")
    .select("id")
    .eq("origem", "conexao")
    .eq("usuario_id", usuarioId)
    .eq("company_id", companyId)
    .is("revogada_em", null)
    .order("criada_em", { ascending: false })
    .range(MAXIMO_DE_CONEXOES, MAXIMO_DE_CONEXOES + 50);
  if (error) {
    console.error("[oauth] não foi possível contar as conexões:", error.message);
    return;
  }
  const sobrando = ((data ?? []) as Array<{ id: string }>).map((linha) => linha.id);
  if (!sobrando.length) return;
  const { error: erroAoAparar } = await desligarConexoes(admin).in("id", sobrando);
  if (erroAoAparar) console.error("[oauth] não foi possível aparar as conexões antigas:", erroAoAparar.message);
}

type ConexaoParaRenovar = {
  id: string;
  hash: string;
  usuario_id: string;
  company_id: string;
  escopos: string[] | null;
  cliente_oauth_id: string | null;
  revogada_em: string | null;
  renovacao_hash: string | null;
  renovacao_trocada_em: string | null;
  renovacao_expira_em: string | null;
  acesso_expira_em: string | null;
};

const COLUNAS_DA_CONEXAO =
  "id,hash,usuario_id,company_id,escopos,cliente_oauth_id,revogada_em,renovacao_hash,renovacao_trocada_em,renovacao_expira_em,acesso_expira_em";

const SEM_RENOVACAO = "Renovação inválida ou vencida. Conecte de novo.";
const COPIA_DESFEITA = "Esta renovação já foi trocada. Por segurança a conexão foi desfeita; conecte de novo.";

/* As renovações trocadas ficam guardadas enquanto a conexão pode viver, e um
   pouco mais. */
const APOSENTADA_VALE_POR_MS = CONEXAO_PARADA_VALE_POR_MS + 10 * 24 * 60 * 60 * 1000;

/**
 * A pessoa ainda faz parte da empresa desta conexão? `null` quando não deu
 * para saber agora (o banco não respondeu): nesse caso nada é desfeito.
 *
 * A conferência completa (permissão, assinatura, empresa liberada) acontece a
 * cada pedido ao conector. Aqui vale só o vínculo, para uma conexão de quem
 * saiu da empresa não continuar se renovando por meses.
 */
async function aindaFazParte(admin: SupabaseClient, usuarioId: string, companyId: string): Promise<boolean | null> {
  if (usuarioId === companyId) return true;
  const [dono, vinculo] = await Promise.all([
    admin.from("companies").select("id").eq("id", companyId).eq("owner_id", usuarioId).maybeSingle(),
    admin.from("company_users").select("active").eq("user_id", usuarioId).eq("company_id", companyId).maybeSingle(),
  ]);
  if (dono.error || vinculo.error) return null;
  if (dono.data) return true;
  return Boolean(vinculo.data) && (vinculo.data as { active?: boolean | null }).active !== false;
}

/**
 * Troca a renovação por um acesso novo e uma renovação nova.
 *
 * As renovações de uma conexão formam uma corrente: cada uma sai da anterior,
 * e o acesso sai da renovação que o acompanha (ver `fichas.ts`). Com isso o
 * servidor responde igual a quem pergunta igual, sem guardar segredo nenhum:
 *
 * - renovação atual, com o acesso recém-nascido: devolve o mesmo par, sem
 *   trocar nada;
 * - renovação atual: troca pelo par seguinte, se a pessoa ainda for da empresa;
 * - renovação anterior, dentro da folga: devolve o par que a troca dela já
 *   tinha produzido. É o programa que não recebeu a resposta, ou a segunda
 *   janela dele pedindo junto;
 * - qualquer renovação já trocada, fora disso: alguém guardou uma cópia. A
 *   conexão é desfeita. Toda renovação trocada fica guardada (só o resumo)
 *   para isso: lembrar só da anterior deixaria quem copiou trocar duas vezes
 *   seguidas e fazer a original sumir de vista.
 */
export async function renovarAcesso(admin: SupabaseClient, pedido: { renovacao: unknown; programa: ProgramaRegistrado }): Promise<Desfecho> {
  if (!pareceRenovacao(pedido.renovacao)) return recusa("invalid_grant", "Renovação inválida.");
  const segredo = segredoDasFichas();
  const recebida = resumoDaFicha(pedido.renovacao);
  const agora = Date.now();

  const { data: atual, error } = await admin.from("chaves_de_acesso").select(COLUNAS_DA_CONEXAO).eq("renovacao_hash", recebida).eq("origem", "conexao").maybeSingle();
  if (error) {
    console.error("[oauth] não foi possível renovar:", error.message);
    return recusa("server_error", "Não foi possível renovar agora.");
  }

  const seguinte = renovacaoSeguinte(segredo, pedido.renovacao);

  if (atual) {
    const conexao = atual as ConexaoParaRenovar;
    const expira = conexao.renovacao_expira_em ? new Date(conexao.renovacao_expira_em).getTime() : 0;
    if (conexao.revogada_em || conexao.cliente_oauth_id !== pedido.programa.id || expira <= agora) return recusa("invalid_grant", SEM_RENOVACAO);

    /* O acesso que acompanha esta renovação acabou de nascer: devolve o mesmo. */
    const acessoAtual = acessoDaRenovacao(segredo, pedido.renovacao);
    const venceEm = conexao.acesso_expira_em ? new Date(conexao.acesso_expira_em).getTime() : 0;
    if (mesmoResumo(conexao.hash, acessoAtual.hash) && venceEm - agora > ACESSO_VALE_POR_MS - REPOUSO_DA_RENOVACAO_MS) {
      return respostaDeAcesso(acessoAtual.valor, pedido.renovacao, conexao.escopos, venceEm - agora);
    }

    /* Quem saiu da empresa não renova: a conexão é desfeita aqui, e não volta
       a valer se a pessoa for readmitida. */
    const vinculo = await aindaFazParte(admin, conexao.usuario_id, conexao.company_id);
    if (vinculo === null) return recusa("server_error", "Não foi possível renovar agora.");
    if (!vinculo) {
      const { error: erroAoDesligar } = await desligarConexoes(admin).eq("id", conexao.id);
      if (erroAoDesligar) console.error("[oauth] não foi possível desfazer a conexão de quem saiu:", erroAoDesligar.message);
      return recusa("invalid_grant", "A pessoa desta conexão não faz mais parte da empresa. A conexão foi desfeita.");
    }

    /* Trocar e conferir acontecem na mesma instrução, para dois pedidos juntos
       não trocarem os dois: o segundo não acha mais a renovação atual e cai no
       caminho da anterior, logo abaixo. */
    const novoAcesso = acessoDaRenovacao(segredo, seguinte.valor);
    const { data: trocada, error: erroAoTrocar } = await admin
      .from("chaves_de_acesso")
      .update({
        hash: novoAcesso.hash,
        prefixo: novoAcesso.prefixo,
        acesso_expira_em: new Date(agora + ACESSO_VALE_POR_MS).toISOString(),
        renovacao_hash: seguinte.hash,
        renovacao_anterior_hash: recebida,
        renovacao_trocada_em: new Date(agora).toISOString(),
        renovacao_expira_em: new Date(agora + CONEXAO_PARADA_VALE_POR_MS).toISOString(),
      })
      .eq("id", conexao.id)
      .eq("renovacao_hash", recebida)
      .is("revogada_em", null)
      .select("id")
      .maybeSingle();
    if (erroAoTrocar) {
      console.error("[oauth] não foi possível renovar:", erroAoTrocar.message);
      return recusa("server_error", "Não foi possível renovar agora.");
    }
    if (trocada) {
      const { error: erroAoGuardar } = await admin.from("renovacoes_aposentadas").upsert({ hash: recebida, chave_id: conexao.id }, { onConflict: "hash", ignoreDuplicates: true });
      if (erroAoGuardar) console.error("[oauth] não foi possível guardar a renovação trocada:", erroAoGuardar.message);
      if (Math.random() < CHANCE_DE_FAXINA) {
        const { error: erroDaFaxina } = await admin.from("renovacoes_aposentadas").delete().lt("aposentada_em", new Date(agora - APOSENTADA_VALE_POR_MS).toISOString());
        if (erroDaFaxina) console.error("[oauth] não foi possível limpar as renovações antigas:", erroDaFaxina.message);
      }
      return respostaDeAcesso(novoAcesso.valor, seguinte.valor, conexao.escopos, ACESSO_VALE_POR_MS);
    }
  }

  /* Não é (ou deixou de ser, neste instante) a atual. Pode ser a anterior. */
  const { data: anterior, error: erroAoLer } = await admin
    .from("chaves_de_acesso")
    .select(COLUNAS_DA_CONEXAO)
    .eq("renovacao_anterior_hash", recebida)
    .eq("origem", "conexao")
    .limit(1)
    .maybeSingle();
  if (erroAoLer) {
    console.error("[oauth] não foi possível conferir a renovação anterior:", erroAoLer.message);
    return recusa("server_error", "Não foi possível renovar agora.");
  }
  const conexao = (anterior ?? null) as ConexaoParaRenovar | null;

  if (conexao && !conexao.revogada_em && conexao.cliente_oauth_id === pedido.programa.id) {
    const trocadaEm = conexao.renovacao_trocada_em ? new Date(conexao.renovacao_trocada_em).getTime() : 0;
    const venceEm = conexao.acesso_expira_em ? new Date(conexao.acesso_expira_em).getTime() : 0;
    if (agora - trocadaEm <= FOLGA_DA_RENOVACAO_MS) {
      const acessoSeguinte = acessoDaRenovacao(segredo, seguinte.valor);
      if (mesmoResumo(conexao.renovacao_hash, seguinte.hash) && mesmoResumo(conexao.hash, acessoSeguinte.hash) && venceEm > agora) {
        return respostaDeAcesso(acessoSeguinte.valor, seguinte.valor, conexao.escopos, venceEm - agora);
      }
      /* Dentro da folga, mas a corrente não fecha (a conexão já andou mais um
         passo, ou o segredo do servidor mudou no meio). Não é sinal de cópia:
         só não dá para repetir a resposta. */
      return recusa("invalid_grant", SEM_RENOVACAO);
    }
  }

  /* Uma renovação já trocada, reaparecendo fora da folga, venha de que
     programa vier: alguém guardou uma cópia. A conexão de onde ela veio é
     desfeita, e a pessoa conecta de novo. */
  const { data: aposentada, error: erroAoProcurar } = await admin.from("renovacoes_aposentadas").select("chave_id").eq("hash", recebida).maybeSingle();
  if (erroAoProcurar) {
    console.error("[oauth] não foi possível conferir as renovações trocadas:", erroAoProcurar.message);
    return recusa("server_error", "Não foi possível renovar agora.");
  }
  const daConexao = (aposentada as { chave_id?: string } | null)?.chave_id ?? (conexao && !conexao.revogada_em ? conexao.id : null);
  if (!daConexao) return recusa("invalid_grant", SEM_RENOVACAO);

  const { error: erroAoDesligar } = await desligarConexoes(admin).eq("id", daConexao);
  if (erroAoDesligar) console.error("[oauth] não foi possível desfazer a conexão da renovação repetida:", erroAoDesligar.message);
  return recusa("invalid_grant", COPIA_DESFEITA);
}

/**
 * O programa avisa que não vai mais usar a conexão. Não diz se achou ou não: a
 * resposta é sempre a mesma. O que não tem a marca do servidor nem vai ao banco.
 */
export async function desligarPelaFicha(admin: SupabaseClient, pedido: { ficha: unknown; programa: ProgramaRegistrado }) {
  if (typeof pedido.ficha !== "string") return;
  const ehRenovacao = pareceRenovacao(pedido.ficha);
  if (!ehRenovacao && !pareceChave(pedido.ficha)) return;
  if (!temMarca(segredoDasFichas(), pedido.ficha)) return;
  const consulta = ehRenovacao
    ? desligarConexoes(admin).eq("renovacao_hash", resumoDaFicha(pedido.ficha))
    : desligarConexoes(admin).eq("hash", hashDaChave(pedido.ficha));
  const { error } = await consulta.eq("cliente_oauth_id", pedido.programa.id);
  if (error) console.error("[oauth] não foi possível desligar pela ficha:", error.message);
}
