import { NextResponse } from "next/server";

import { autenticar, ErroDeAcesso, type ContextoDoAssistente } from "@/lib/assistentes/contexto";
import { lerCorpoComLimite } from "@/lib/assistentes/corpo";
import { chamarFerramenta, ferramentasPara, INSTRUCOES } from "@/lib/assistentes/ferramentas";
import { enderecosDoConector } from "@/lib/assistentes/oauth/enderecos";
import { cabecalhosDeCors, respostaAoPreflight } from "@/lib/assistentes/oauth/http";
import { caminhoUsado, enderecoDosMetadadosDoRecurso } from "@/lib/assistentes/oauth/metadados";
import { clienteDoInitialize, tratarCorpo, VERSOES_ACEITAS, type Servidor } from "@/lib/assistentes/protocolo";

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

/**
 * O servidor MCP da Ktalogu.
 *
 * É por aqui que o Claude, o ChatGPT, o Codex ou um programa do cliente
 * conversam com a conta. Em produção o endereço é mcp.ktalogu.com.br, que o
 * `proxy.ts` encaminha para esta rota.
 *
 * Um POST traz uma mensagem do protocolo e leva de volta um JSON. O acesso vem
 * no cabeçalho `Authorization` (uma chave criada no painel, ou o acesso que o
 * botão "Conectar" entregou) e é conferido antes de qualquer coisa, inclusive
 * antes do `initialize`: sem acesso válido, o servidor nem diz quais
 * ferramentas existem.
 *
 * A recusa por falta de acesso é também o começo do login por botão: ela diz
 * ao programa onde está o cartão de visita do conector, e é a partir dele que
 * o programa abre a tela de permitir.
 */

const TAMANHO_MAXIMO = 256 * 1024;
const SEM_CACHE = { "Cache-Control": "no-store" };

function recusa(request: Request, status: number, mensagem: string, cabecalhos: Record<string, string> = {}) {
  return NextResponse.json(
    { jsonrpc: "2.0", id: null, error: { code: -32001, message: mensagem } },
    { status, headers: { ...SEM_CACHE, ...cabecalhosDeCors(request), ...cabecalhos } }
  );
}

/**
 * O que acompanha um 401. `comErro` separa quem mandou um acesso que não vale
 * (o programa deve renovar ou conectar de novo) de quem não mandou nenhum.
 * O cabeçalho só aceita ASCII: a explicação em português vai no corpo.
 */
function desafio(request: Request, comErro: boolean) {
  const cartao = enderecoDosMetadadosDoRecurso(enderecosDoConector(request), caminhoUsado(request.headers));
  const partes = ['Bearer realm="Ktalogu"'];
  if (comErro) partes.push('error="invalid_token"');
  partes.push(`resource_metadata="${cartao}"`, 'scope="leitura escrita"');
  return partes.join(", ");
}

export async function POST(request: Request) {
  const bruto = await lerCorpoComLimite(request, TAMANHO_MAXIMO);
  if (bruto === null) return recusa(request, 413, "Pedido grande demais.");

  let corpo: unknown;
  try {
    corpo = JSON.parse(bruto);
  } catch {
    return NextResponse.json(
      { jsonrpc: "2.0", id: null, error: { code: -32700, message: "O corpo do pedido não é um JSON válido." } },
      { status: 400, headers: { ...SEM_CACHE, ...cabecalhosDeCors(request) } }
    );
  }

  /* Depois do `initialize`, o cliente diz em cada pedido a versão combinada. */
  const versao = (request.headers.get("mcp-protocol-version") || "").trim();
  if (versao && !(VERSOES_ACEITAS as readonly string[]).includes(versao)) {
    return recusa(request, 400, `Versão do protocolo não atendida: ${versao}.`);
  }

  let ctx: ContextoDoAssistente;
  try {
    ctx = await autenticar(request, clienteDoInitialize(corpo));
  } catch (erro) {
    if (erro instanceof ErroDeAcesso) {
      const cabecalhos: Record<string, string> | undefined =
        erro.status === 401
          ? { "WWW-Authenticate": desafio(request, erro.codigo !== "sem_chave") }
          : erro.status === 429
            ? { "Retry-After": "60" }
            : undefined;
      return recusa(request, erro.status, erro.message, cabecalhos);
    }
    console.error("[mcp] falha ao conferir o acesso:", erro instanceof Error ? erro.message : erro);
    return recusa(request, 500, "Erro interno ao conferir o acesso.");
  }

  const servidor: Servidor = {
    nome: "ktalogu",
    titulo: "Ktalogu",
    versao: "1.0.0",
    instrucoes: INSTRUCOES,
    ferramentas: () => ferramentasPara(ctx),
    chamar: (nome, argumentos) => chamarFerramenta(ctx, nome, argumentos),
  };

  const { corpo: resposta } = await tratarCorpo(corpo, servidor);
  if (resposta === null) return new NextResponse(null, { status: 202, headers: { ...SEM_CACHE, ...cabecalhosDeCors(request) } });
  return NextResponse.json(resposta, { headers: { ...SEM_CACHE, ...cabecalhosDeCors(request) } });
}

/* Quem abre o endereço no navegador vê para que ele serve, em vez de um erro. */
const PAGINA = `<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex">
<title>Conector da Ktalogu</title>
<style>
  body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: #2e1065; color: #fff; font: 16px/1.6 system-ui, -apple-system, "Segoe UI", sans-serif; }
  main { max-width: 34rem; padding: 2rem; }
  h1 { margin: 0 0 .75rem; font-size: 1.75rem; line-height: 1.15; letter-spacing: -.02em; }
  p { margin: 0 0 1rem; color: #d6c9fb; }
  a { color: #fff; font-weight: 700; }
</style>
</head>
<body>
<main>
  <h1>Este é o conector da Ktalogu.</h1>
  <p>É por este endereço que o Claude, o ChatGPT, o Codex e outros programas conversam com a sua conta. Ele não abre no navegador.</p>
  <p>Para ligar um assistente, copie este endereço e cole no assistente, na parte de conectores. O passo a passo de cada um está no painel, em Configurações, Integrações, Assistentes e Apps.</p>
  <p><a href="https://admin.ktalogu.com.br/dashboard/settings/shipping?aba=assistentes">Abrir o painel</a></p>
</main>
</body>
</html>`;

export async function GET(request: Request) {
  const aceita = request.headers.get("accept") || "";
  if (aceita.includes("text/html")) {
    return new NextResponse(PAGINA, { status: 200, headers: { ...SEM_CACHE, "Content-Type": "text/html; charset=utf-8" } });
  }
  /* O protocolo prevê um canal de eventos por GET. Este servidor não usa: toda
     resposta sai no próprio POST. */
  return recusa(request, 405, "Este servidor atende por POST.", { Allow: "POST" });
}

export async function DELETE(request: Request) {
  return recusa(request, 405, "Este servidor não guarda sessão.", { Allow: "POST" });
}

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