import { temMarca } from "@/lib/assistentes/chave";
import { lembrarAcessoBom } from "@/lib/assistentes/contexto";
import { enderecosDoConector } from "@/lib/assistentes/oauth/enderecos";
import { codigoComMarca, pareceRenovacao } from "@/lib/assistentes/oauth/fichas";
import { credenciaisDoPrograma, freios, json, lerCampos, recusaOAuth, respostaAoPreflight } from "@/lib/assistentes/oauth/http";
import { origensDoConector } from "@/lib/assistentes/oauth/metadados";
import { identidadeDoPrograma, recursoAceito } from "@/lib/assistentes/oauth/pedido";
import { buscarPrograma, programaConfere, renovarAcesso, segredoDasFichas, trocarCodigo, type Desfecho } from "@/lib/assistentes/oauth/servidor";
import { enderecoDeQuemChama } from "@/lib/assistentes/tentativas";
import { createAdminClient } from "@/lib/supabase-server";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

/**
 * A troca: o programa entrega o código que recebeu quando a pessoa clicou em
 * "Permitir" e leva o acesso; depois, de hora em hora, entrega a renovação e
 * leva o acesso seguinte.
 *
 * Quem chama é o programa, direto, sem navegador e sem sessão. Ele prova que
 * é o mesmo que começou a conexão mostrando o segredo do PKCE.
 *
 * O que chega sem a marca do servidor é recusado antes de qualquer ida ao
 * banco. A exceção é a renovação: se o segredo do servidor for trocado um dia,
 * as renovações antigas ficam sem marca válida, e ainda assim têm de poder
 * renovar. Essas passam por um freio por endereço, que só elas enfrentam.
 */
export async function POST(request: Request) {
  const campos = await lerCampos(request);
  if (!campos) return recusaOAuth(request, 400, "invalid_request", "O pedido precisa ser um formulario de ate 32 KB.");

  const quemChama = enderecoDeQuemChama(request.headers);
  const { falhas } = freios();
  const segredo = segredoDasFichas();
  const concessao = campos.grant_type;

  let semMarca = false;
  if (concessao === "authorization_code") {
    if (!codigoComMarca(segredo, campos.code)) return recusaOAuth(request, 400, "invalid_grant", "Codigo invalido.");
  } else if (concessao === "refresh_token") {
    if (!pareceRenovacao(campos.refresh_token)) return recusaOAuth(request, 400, "invalid_grant", "Renovacao invalida.");
    semMarca = !temMarca(segredo, campos.refresh_token);
    if (semMarca && falhas.bloqueado(quemChama)) {
      return recusaOAuth(request, 429, "temporarily_unavailable", "Muitas tentativas recusadas vindas deste endereco. Espere um minuto.", { "Retry-After": "60" });
    }
  } else {
    return recusaOAuth(request, 400, "unsupported_grant_type", "Use grant_type=authorization_code ou grant_type=refresh_token.");
  }

  const credenciais = credenciaisDoPrograma(request, campos);
  const identidade = identidadeDoPrograma(credenciais.clientId);
  const admin = createAdminClient();
  const programa = identidade ? await buscarPrograma(admin, identidade) : null;
  if (!programa || !programaConfere(programa, credenciais.segredo)) {
    if (semMarca) falhas.anotar(quemChama);
    return recusaOAuth(
      request,
      401,
      "invalid_client",
      "Programa desconhecido ou segredo incorreto.",
      credenciais.porCabecalho ? { "WWW-Authenticate": 'Basic realm="Ktalogu"' } : {}
    );
  }

  if (!recursoAceito(campos.resource, origensDoConector(enderecosDoConector(request)))) {
    return recusaOAuth(request, 400, "invalid_target", "O parametro resource nao aponta para este servidor.");
  }

  const desfecho: Desfecho =
    concessao === "authorization_code"
      ? await trocarCodigo(admin, { codigo: campos.code, verificador: campos.code_verifier, redirectUri: campos.redirect_uri ?? null, programa })
      : await renovarAcesso(admin, { renovacao: campos.refresh_token, programa });

  if (!desfecho.ok) {
    if (desfecho.erro === "server_error") return recusaOAuth(request, 503, "server_error", desfecho.descricao, { "Retry-After": "5" });
    if (semMarca) falhas.anotar(quemChama);
    return recusaOAuth(request, 400, desfecho.erro, desfecho.descricao);
  }

  lembrarAcessoBom(desfecho.resposta.access_token);
  return json(request, desfecho.resposta);
}

export function OPTIONS(request: Request) {
  return respostaAoPreflight(request);
}
