import { NextResponse } from "next/server";
import sharp from "sharp";

import { FORMATO_DO_CODIGO, FORMATOS_DO_ENVIO, nomeDoArquivoEnviado } from "@/lib/assistentes/codigo-de-envio";
import { enderecoDeQuemChama, faixaDoEndereco } from "@/lib/assistentes/tentativas";
import { origemDoPedido, origemPublicaDoAdmin } from "@/lib/origem-publica";
import { createAdminClient } from "@/lib/supabase-server";

/*
 * Recebe a imagem da página /enviar/<código> (migration 192). Sem login: quem
 * tem o link envia, como no link curto dos arquivos. Confere o código e a
 * validade, o tamanho (15 MB) e o conteúdo de verdade (o sharp lê o arquivo;
 * o nome e o tipo que o navegador diz não contam), e guarda no depósito
 * privado. Mandar de novo pelo mesmo link troca a imagem.
 */

export const dynamic = "force-dynamic";

const BALDE = "assistentes-arquivos";
const LIMITE_EM_BYTES = 15 * 1024 * 1024;
/* O formulário leva umas centenas de bytes além do arquivo. */
const FOLGA_DO_FORMULARIO = 64 * 1024;
const ENVIOS_POR_CODIGO = 10;
const ENVIOS_POR_ENDERECO_POR_HORA = 60;
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 });
}
const erro = (mensagem: string, status: number) => resposta({ ok: false, erro: mensagem }, status);

/* Contadores na memória do processo, como os freios do conector: somem quando
   o servidor reinicia, e tudo bem (o código ainda vence em 24 horas). */
declare global {
  var __enviosPorCodigo: Map<string, { quantos: number; desde: number }> | undefined;
  var __enviosPorEndereco: Map<string, { quantos: number; desde: number }> | undefined;
}

function contar(mapa: Map<string, { quantos: number; desde: number }>, chave: string, limite: number, janelaMs: number) {
  const agora = Date.now();
  /* Faxina de passagem: o mapa não cresce sem fim. */
  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. Sem o
   cabeçalho Origin (programa de linha de comando) segue; o código ainda manda. */
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(/\/$/, ""));
}

export async function POST(request: Request, { params }: { params: Promise<{ codigo: string }> }) {
  const { codigo } = await params;
  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);

  const tamanhoDito = Number(request.headers.get("content-length") || 0);
  if (tamanhoDito > LIMITE_EM_BYTES + FOLGA_DO_FORMULARIO) return erro("A imagem passa de 15 MB. Escolha uma menor.", 413);

  globalThis.__enviosPorEndereco ??= new Map();
  globalThis.__enviosPorCodigo ??= new Map();
  const faixa = faixaDoEndereco(enderecoDeQuemChama(request.headers)) ?? "sem-endereco";
  if (!contar(globalThis.__enviosPorEndereco, faixa, ENVIOS_POR_ENDERECO_POR_HORA, HORA_MS)) {
    return erro("Muitos envios em pouco tempo. Espere um pouco e tente de novo.", 429);
  }

  const admin = createAdminClient();
  const { data, error } = await admin
    .from("assistentes_envios")
    .select("codigo,company_id,caminho,vence_em,tipo")
    .eq("codigo", codigo)
    .maybeSingle();
  if (error) {
    console.error("[enviar] ler envio", error.message);
    return erro("Não deu para receber agora. Tente de novo em instantes.", 503);
  }
  const envio = data as { codigo: string; company_id: string; caminho: string | null; vence_em: string; tipo: string | null } | null;
  if (!envio) return erro("Este link de envio não existe. Peça um link novo ao assistente.", 404);
  /* O link de vídeo ou arquivo digital (pedir_video) envia pela rota /digital, direto ao depósito. */
  if (envio.tipo?.startsWith("digital:")) return erro("Este link é para enviar um vídeo ou arquivo, não uma imagem. Abra o link de novo.", 409);
  if (new Date(envio.vence_em).getTime() <= Date.now()) return erro("Este link venceu. Peça um link novo ao assistente.", 410);
  if (!contar(globalThis.__enviosPorCodigo, codigo, ENVIOS_POR_CODIGO, 24 * HORA_MS)) {
    return erro("Este link já recebeu envios demais. Peça um link novo ao assistente.", 429);
  }

  let arquivo: File | null = null;
  try {
    const formulario = await request.formData();
    const campo = formulario.get("arquivo");
    arquivo = campo instanceof File ? campo : null;
  } catch {
    return erro("O envio chegou incompleto. Tente de novo (se a imagem for muito grande, escolha uma menor).", 400);
  }
  if (!arquivo || arquivo.size === 0) return erro("Nenhuma imagem chegou. Escolha a imagem e envie de novo.", 400);
  if (arquivo.size > LIMITE_EM_BYTES) return erro("A imagem passa de 15 MB. Escolha uma menor.", 413);
  const corpo = Buffer.from(await arquivo.arrayBuffer());
  if (corpo.length > LIMITE_EM_BYTES) return erro("A imagem passa de 15 MB. Escolha uma menor.", 413);

  let meta: sharp.Metadata;
  try {
    meta = await sharp(corpo, { limitInputPixels: 60_000_000, failOn: "error", animated: false }).metadata();
  } catch {
    return erro("Este arquivo não é uma imagem que dê para usar. Envie PNG, JPG ou WebP.", 415);
  }
  if (meta.format === "svg") return erro("Imagem em SVG não é aceita. Envie PNG, JPG ou WebP.", 415);
  /* O sharp chama o AVIF de "heif" (mesmo invólucro); a compressão diz qual é. */
  const formato = meta.format === "heif" && meta.compression === "av1" ? "avif" : meta.format;
  const tipo = formato ? FORMATOS_DO_ENVIO[formato] : undefined;
  if (!tipo) return erro("Este formato de imagem não é aceito. Envie PNG, JPG ou WebP.", 415);
  const largura = meta.width ?? 0;
  const altura = meta.height ?? 0;
  if (!largura || !altura) return erro("Não deu para ler o tamanho desta imagem. Envie outra.", 415);

  const nome = nomeDoArquivoEnviado(arquivo.name, tipo.extensao);
  const caminho = `${envio.company_id}/entrada/${codigo}/${nome}`;
  const deposito = admin.storage.from(BALDE);
  const { error: erroAoGuardar } = await deposito.upload(caminho, corpo, { contentType: tipo.tipo, upsert: true });
  if (erroAoGuardar) {
    console.error("[enviar] guardar imagem", erroAoGuardar.message);
    return erro("Não deu para guardar a imagem agora. Tente de novo em instantes.", 503);
  }
  /* Mandou outra imagem pelo mesmo link: a anterior sai (só se for desta pasta). */
  if (envio.caminho && envio.caminho !== caminho && envio.caminho.startsWith(`${envio.company_id}/entrada/${codigo}/`)) {
    const { error: erroAoTrocar } = await deposito.remove([envio.caminho]);
    if (erroAoTrocar) console.error("[enviar] apagar imagem anterior", erroAoTrocar.message);
  }

  const { error: erroAoGravar } = await admin
    .from("assistentes_envios")
    .update({ caminho, nome, tipo: tipo.tipo, tamanho: corpo.length, largura, altura, recebido_em: new Date().toISOString() })
    .eq("codigo", codigo)
    .eq("company_id", envio.company_id);
  if (erroAoGravar) {
    console.error("[enviar] gravar envio", erroAoGravar.message);
    return erro("A imagem chegou, mas não deu para registrar. Tente de novo em instantes.", 503);
  }
  return resposta({ ok: true, largura, altura });
}
