import { NextResponse } from "next/server";

import { FORMATO_DO_CODIGO } from "@/lib/assistentes/codigo-de-envio";
import { abrirEnvioDigital, cancelarEnvioDigital, concluirEnvioDigital, lerEnvioDigital, retomarEnvioDigital } from "@/lib/assistentes/envio-digital";
import { enderecoDeQuemChama, faixaDoEndereco } from "@/lib/assistentes/tentativas";
import { ErroDigital } from "@/lib/digitais/servidor/contexto";
import { origemDoPedido, origemPublicaDoAdmin } from "@/lib/origem-publica";
import { createAdminClient } from "@/lib/supabase-server";

/*
 * O vídeo (ou arquivo) que a pessoa manda ao assistente pela página
 * /enviar/<código> (lib/assistentes/envio-digital.ts). Sem login: o código
 * manda. Os bytes não passam por aqui: o navegador recebe endereços
 * assinados e manda as partes direto para o depósito, como na biblioteca do
 * painel (components/digitais/enviar-arquivo.ts usa estas rotas).
 *
 *   POST                       abre o envio ({ nome, tamanho })
 *   POST ?etapa=partes&arquivo  retoma (endereços novos para o que falta)
 *   POST ?etapa=concluir&arquivo  fecha e confere ({ meta })
 *   DELETE ?arquivo             cancela o envio que não terminou
 */

export const dynamic = "force-dynamic";

const ABERTURAS_POR_CODIGO = 10;
const PEDIDOS_POR_ENDERECO_POR_HORA = 300;
const HORA_MS = 60 * 60 * 1000;
const SEM_CACHE = { "Cache-Control": "no-store", "X-Robots-Tag": "noindex", "Referrer-Policy": "no-referrer" };

function resposta(corpo: Record<string, unknown>, status = 200) {
  return NextResponse.json(corpo, { status, headers: SEM_CACHE });
}
/* `error`: o mesmo campo das rotas do painel, que o envio em partes sabe mostrar. */
const erro = (mensagem: string, status: number) => resposta({ ok: false, error: mensagem }, status);

declare global {
  var __aberturasDigitaisPorCodigo: Map<string, { quantos: number; desde: number }> | undefined;
  var __enviosDigitaisPorEndereco: Map<string, { quantos: number; desde: number }> | undefined;
}

/* Contadores na memória do processo, como os do envio de imagem: somem quando
   o servidor reinicia, e tudo bem (o código ainda vence em 24 horas). */
function contar(mapa: Map<string, { quantos: number; desde: number }>, chave: string, limite: number, janelaMs: number) {
  const agora = Date.now();
  if (mapa.size > 5000) {
    for (const [outra, valor] of mapa) if (valor.desde < agora - janelaMs) mapa.delete(outra);
  }
  const atual = mapa.get(chave);
  if (!atual || atual.desde < agora - janelaMs) {
    mapa.set(chave, { quantos: 1, desde: agora });
    return true;
  }
  if (atual.quantos >= limite) return false;
  atual.quantos += 1;
  return true;
}

/* Pedido de outro site não passa: a página de envio é deste endereço. */
function origemAceita(request: Request) {
  const origem = request.headers.get("origin");
  if (!origem) return true;
  const permitidas = new Set([origemDoPedido(request), origemPublicaDoAdmin(request)].map((o) => o.replace(/\/$/, "")));
  return permitidas.has(origem.replace(/\/$/, ""));
}

async function atender(request: Request, codigo: string, fazer: (envio: Awaited<ReturnType<typeof lerEnvioDigital>>, url: URL) => Promise<unknown>) {
  if (!FORMATO_DO_CODIGO.test(codigo || "")) return erro("Link de envio inválido. Peça um link novo ao assistente.", 404);
  if (!origemAceita(request)) return erro("Envio recusado.", 403);
  globalThis.__enviosDigitaisPorEndereco ??= new Map();
  const faixa = faixaDoEndereco(enderecoDeQuemChama(request.headers)) ?? "sem-endereco";
  if (!contar(globalThis.__enviosDigitaisPorEndereco, faixa, PEDIDOS_POR_ENDERECO_POR_HORA, HORA_MS)) {
    return erro("Muitos envios em pouco tempo. Espere um pouco e tente de novo.", 429);
  }
  try {
    const envio = await lerEnvioDigital(createAdminClient(), codigo);
    const resultado = await fazer(envio, new URL(request.url));
    return resposta({ ok: true, ...((resultado ?? {}) as Record<string, unknown>) });
  } catch (falha) {
    if (falha instanceof ErroDigital) return resposta({ ok: false, error: falha.message, ...(falha.extra ?? {}) }, falha.status);
    console.error("[enviar] envio digital", falha instanceof Error ? falha.message : falha);
    return erro("Algo deu errado do nosso lado. Tente de novo em instantes.", 500);
  }
}

const idDoArquivo = (url: URL) => {
  const valor = String(url.searchParams.get("arquivo") || "");
  return /^[0-9a-f-]{36}$/i.test(valor) ? valor : null;
};

export async function POST(request: Request, { params }: { params: Promise<{ codigo: string }> }) {
  const { codigo } = await params;
  return atender(request, codigo, async (envio, url) => {
    const admin = createAdminClient();
    const etapa = url.searchParams.get("etapa");
    const corpo = await request.json().catch(() => null);
    if (etapa === "partes") return retomarEnvioDigital(admin, envio, idDoArquivo(url));
    if (etapa === "concluir") return concluirEnvioDigital(admin, envio, idDoArquivo(url), corpo, request);
    globalThis.__aberturasDigitaisPorCodigo ??= new Map();
    if (!contar(globalThis.__aberturasDigitaisPorCodigo, codigo, ABERTURAS_POR_CODIGO, 24 * HORA_MS)) {
      throw new ErroDigital("Este link já recebeu envios demais. Peça um link novo ao assistente.", 429);
    }
    return abrirEnvioDigital(admin, envio, corpo);
  });
}

export async function DELETE(request: Request, { params }: { params: Promise<{ codigo: string }> }) {
  const { codigo } = await params;
  return atender(request, codigo, (envio, url) => cancelarEnvioDigital(createAdminClient(), envio, idDoArquivo(url)));
}
