import { createHash } from "node:crypto";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import { lerAparelho, origemDoLink } from "@/lib/aparelho";
import { createAdminClient } from "@/lib/supabase-admin";

export const runtime = "nodejs";

/*
  A leitura do link do orçamento.

  A página do orçamento avisa aqui quando abre ("abriu"), de tempos em tempos
  enquanto a pessoa lê ("sinal": tempo de leitura, até onde rolou, que partes
  apareceram) e quando ela clica em algo que importa ("acao": baixar o PDF,
  começar a aprovar, aprovar, abrir as artes, acompanhar o pedido).

  Quem grava é o banco (registrar_abertura_do_link e registrar_sinal_do_link),
  que confere o link do mesmo jeito que a página e continua contando as
  visualizações pela função de sempre. Aqui só se lê o pedido, o aparelho e a
  marca da equipe: o navegador em que alguém da empresa usa o painel leva o
  cookie "ktl_equipe", e a abertura dele não conta como do cliente.

  Não se guarda IP. A cidade só entra se a Cloudflare mandar.
*/

const COOKIE_DA_EQUIPE = "ktl_equipe";
const ACOES = new Set(["pdf", "aprovar", "aprovou", "artes", "acompanhar"]);
const SECOES = new Set(["itens", "total", "condicoes", "observacoes", "fim"]);
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

const texto = (valor: unknown) => (typeof valor === "string" ? valor.trim() : "");

function inteiro(valor: unknown, minimo: number, maximo: number) {
  const numero = Math.round(Number(valor));
  if (!Number.isFinite(numero)) return null;
  return Math.min(maximo, Math.max(minimo, numero));
}

function objeto(valor: unknown): Record<string, unknown> {
  return valor && typeof valor === "object" && !Array.isArray(valor) ? (valor as Record<string, unknown>) : {};
}

/* Cabeçalho HTTP chega como latin-1; a Cloudflare manda a cidade em UTF-8
   ("SÃ£o Paulo" vira "São Paulo" de volta). */
function doCabecalho(valor: string | null) {
  const bruto = texto(valor);
  if (!bruto) return null;
  const consertado = /[ÃÂ]/.test(bruto) ? Buffer.from(bruto, "latin1").toString("utf8") : bruto;
  return consertado.slice(0, 80) || null;
}

async function empresasDaEquipe() {
  const valor = texto((await cookies()).get(COOKIE_DA_EQUIPE)?.value);
  if (!valor) return [] as string[];
  return decodeURIComponent(valor)
    .split(/[,.]/)
    .map((parte) => parte.trim())
    .filter((parte) => UUID.test(parte))
    .slice(0, 20);
}

async function registrarAbertura(corpo: Record<string, unknown>, request: Request, token: string, leitor: string) {
  const tela = objeto(corpo.tela);
  const dicas = objeto(corpo.dicas);
  const aparelho = lerAparelho(request.headers.get("user-agent"), {
    modelo: texto(dicas.modelo).slice(0, 60) || null,
    versao: texto(dicas.versao).slice(0, 20) || null,
    toques: inteiro(tela.toques, 0, 20),
  });
  if (aparelho.robo) return NextResponse.json({ ok: true, ignorado: true });

  const largura = inteiro(tela.largura, 0, 10000);
  const altura = inteiro(tela.altura, 0, 10000);
  /* Em pé ou deitado, a tela é a mesma: guarda sempre o lado menor primeiro. */
  const menor = largura && altura ? Math.min(largura, altura) : null;
  const maior = largura && altura ? Math.max(largura, altura) : null;
  const densidade = Math.round((Number(tela.densidade) || 0) * 100) / 100;
  const fuso = /^[A-Za-z0-9_+\-/]{1,60}$/.test(texto(corpo.fuso)) ? texto(corpo.fuso) : null;
  const idioma = /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})?$/.test(texto(corpo.idioma)) ? texto(corpo.idioma) : null;
  const host = (request.headers.get("host") || "").split(":")[0];

  /* O mesmo aparelho em dois navegadores (o do WhatsApp e o Chrome, por
     exemplo) guarda duas chaves de leitor diferentes, mas a mesma tela, o
     mesmo sistema, o mesmo fuso e o mesmo idioma. É isso que junta os dois. */
  const chaveDoAparelho =
    menor && maior
      ? createHash("sha256")
          .update([aparelho.tipo, aparelho.sistema ?? "", menor, maior, densidade, fuso ?? "", idioma ?? ""].join("|"))
          .digest("hex")
          .slice(0, 16)
      : null;

  const pais = texto(request.headers.get("cf-ipcountry")).toUpperCase();
  const dados = {
    tipo: aparelho.tipo,
    sistema: aparelho.sistema,
    versao: aparelho.versao,
    navegador: aparelho.navegador,
    modelo: aparelho.modelo,
    app: aparelho.app,
    largura: menor ? String(menor) : null,
    altura: maior ? String(maior) : null,
    fuso,
    idioma,
    origem: origemDoLink(texto(corpo.origem), host),
    cidade: doCabecalho(request.headers.get("cf-ipcity")),
    estado: doCabecalho(request.headers.get("cf-region")),
    pais: /^[A-Z]{2}$/.test(pais) && pais !== "XX" && pais !== "T1" ? pais : null,
    agente: texto(request.headers.get("user-agent")).slice(0, 400),
  };

  const admin = createAdminClient();
  const { data, error } = await admin.rpc("registrar_abertura_do_link", {
    p_token: token,
    p_leitor: leitor,
    p_aparelho: chaveDoAparelho,
    p_dados: dados,
    p_equipe: await empresasDaEquipe(),
    p_contar: true,
  });
  if (error) {
    console.error("leitura do orçamento: abertura", error.message);
    return NextResponse.json({ ok: false }, { status: 500 });
  }
  const resposta = objeto(data);
  return NextResponse.json({
    ok: resposta.ok === true,
    id: typeof resposta.id === "string" ? resposta.id : null,
    equipe: resposta.equipe === true,
  });
}

async function registrarSinal(corpo: Record<string, unknown>, token: string, leitor: string, acao: string | null) {
  const visita = texto(corpo.id);
  if (!UUID.test(visita)) return NextResponse.json({ ok: false }, { status: 400 });
  const secoes = Array.isArray(corpo.secoes)
    ? [...new Set(corpo.secoes.map(texto).filter((secao) => SECOES.has(secao)))]
    : [];
  const admin = createAdminClient();
  const { data, error } = await admin.rpc("registrar_sinal_do_link", {
    p_token: token,
    p_visita: visita,
    p_leitor: leitor,
    p_ativo: inteiro(corpo.ativo, 0, 3600) ?? 0,
    p_rolagem: inteiro(corpo.rolagem, 0, 100) ?? 0,
    p_secoes: secoes,
    p_acao: acao,
  });
  if (error) {
    console.error("leitura do orçamento: sinal", error.message);
    return NextResponse.json({ ok: false }, { status: 500 });
  }
  return NextResponse.json({ ok: objeto(data).ok === true });
}

export async function POST(request: Request) {
  /* O navigator.sendBeacon manda como texto; o fetch manda como JSON. Os dois
     chegam aqui como o mesmo corpo. */
  const corpo = objeto(await request.json().catch(() => null));
  const tipo = texto(corpo.tipo);
  const token = texto(corpo.token);
  const leitor = texto(corpo.leitor);
  if (!/^[A-Za-z0-9_-]{8,128}$/.test(token) || !UUID.test(leitor)) {
    return NextResponse.json({ ok: false }, { status: 400 });
  }

  try {
    if (tipo === "abriu") return await registrarAbertura(corpo, request, token, leitor);
    if (tipo === "sinal") return await registrarSinal(corpo, token, leitor, null);
    if (tipo === "acao") {
      const acao = texto(corpo.acao);
      if (!ACOES.has(acao)) return NextResponse.json({ ok: false }, { status: 400 });
      return await registrarSinal(corpo, token, leitor, acao);
    }
  } catch (erro) {
    console.error("leitura do orçamento", erro instanceof Error ? erro.message : erro);
    return NextResponse.json({ ok: false }, { status: 500 });
  }
  return NextResponse.json({ ok: false }, { status: 400 });
}
