import { NextResponse } from "next/server";
import { canCompany, getCompanyContext } from "@/lib/company-context";
import {
  agruparAparelhos,
  chaveDoAparelho,
  FUSO,
  montarChance,
  montarVisitas,
  passosDoLink,
  pontosDaConsulta,
  resumir,
  separarDaEquipe,
  sinaisDoLink,
  type AberturaGuardada,
  type EnvioDoLink,
  type MetricasDoLink,
  type OrcamentoDasMetricas,
} from "@/lib/metricas-do-link";
import { createAdminClient, createClient } from "@/lib/supabase-server";

export const runtime = "nodejs";

/*
  As métricas do link de um orçamento: quem abriu, quando, onde, por quanto
  tempo, o que clicou, e a chance de fechar contada no histórico real.

  As aberturas ficam em tabelas sem acesso direto (só a chave de serviço lê):
  aqui se confere antes que o orçamento é da empresa de quem pede e que a
  pessoa pode ver orçamentos. A conta mora em lib/metricas-do-link.ts.

  POST registra quando a empresa mandou o link ("envio") e marca navegadores
  como da equipe ("fui eu"), ou desfaz a marca.
*/

const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const COLUNAS_DA_ABERTURA =
  "id,viewer_key,device_key,opened_at,last_seen_at,active_seconds,max_scroll,sections,actions,device_type,os_name,os_version,browser_name,device_model,in_app,referrer,city,region,country,is_team,counted";

type OrcamentoLido = {
  id: string;
  user_id: string;
  number: number | null;
  status: string | null;
  created_at: string;
  share_enabled: boolean | null;
  share_expires_at: string | null;
  expires_at: string | null;
  list_price_mode: boolean | null;
  public_view_count: number | null;
  public_first_viewed_at: string | null;
  public_last_viewed_at: string | null;
  client_id: string | null;
};

async function orcamentoDaEmpresa(id: string, permissao: "quotes.read" | "quotes.write") {
  const supabase = await createClient();
  const {
    data: { user },
  } = await supabase.auth.getUser();
  if (!user) return { erro: NextResponse.json({ error: "Não autenticado." }, { status: 401 }) } as const;
  const empresa = await getCompanyContext(supabase, user);
  if (!canCompany(empresa, user.id, permissao)) {
    return { erro: NextResponse.json({ error: "Sem permissão para ver este orçamento." }, { status: 403 }) } as const;
  }
  const tenantIds = empresa.tenantIds?.length ? empresa.tenantIds : [user.id];
  const { data, error } = await supabase
    .from("quotes")
    .select(
      "id,user_id,number,status,created_at,share_enabled,share_expires_at,expires_at,list_price_mode,public_view_count,public_first_viewed_at,public_last_viewed_at,client_id"
    )
    .eq("id", id)
    .in("user_id", tenantIds)
    .maybeSingle();
  if (error || !data) return { erro: NextResponse.json({ error: "Orçamento não encontrado." }, { status: 404 }) } as const;
  return { supabase, user, orcamento: data as OrcamentoLido } as const;
}

function hojeEmSaoPaulo(agora: Date) {
  return new Intl.DateTimeFormat("en-CA", { timeZone: FUSO, year: "numeric", month: "2-digit", day: "2-digit" }).format(agora);
}

export async function GET(_req: Request, contexto: { params: Promise<{ id: string }> }) {
  const { id } = await contexto.params;
  if (!UUID.test(id)) return NextResponse.json({ error: "Orçamento inválido." }, { status: 400 });

  const acesso = await orcamentoDaEmpresa(id, "quotes.read");
  if ("erro" in acesso) return acesso.erro;
  const { supabase, orcamento: linha } = acesso;

  const admin = createAdminClient();
  const [aberturas, envios, equipe, pedido, cliente] = await Promise.all([
    /* As mais recentes primeiro: se um link tiver aberturas demais, o que fica
       de fora é o mais antigo, nunca o de hoje. */
    admin.from("quote_link_views").select(COLUNAS_DA_ABERTURA).eq("quote_id", id).order("opened_at", { ascending: false }).limit(3000),
    admin.from("quote_link_shares").select("channel,created_at").eq("quote_id", id).order("created_at", { ascending: true }).limit(300),
    admin.from("quote_link_team_viewers").select("viewer_key").eq("company_id", linha.user_id).limit(500),
    admin.from("orders").select("created_at").eq("quote_id", id).maybeSingle(),
    linha.client_id
      ? supabase.from("clients").select("name,company_name,whatsapp").eq("id", linha.client_id).maybeSingle()
      : Promise.resolve({ data: null, error: null }),
  ]);
  if (aberturas.error) {
    console.error("métricas do link", aberturas.error.message);
    return NextResponse.json({ error: "Não deu para ler as aberturas do link." }, { status: 500 });
  }

  const dadosDoCliente = (cliente.data ?? null) as { name?: string | null; company_name?: string | null; whatsapp?: string | null } | null;
  const orcamento: OrcamentoDasMetricas = {
    numero: linha.number,
    status: linha.status,
    criadoEm: linha.created_at,
    listaDePrecos: Boolean(linha.list_price_mode),
    linkAtivo: Boolean(linha.share_enabled),
    linkVenceEm: linha.share_expires_at,
    validade: linha.expires_at,
    contadorAntigo: Number(linha.public_view_count) || 0,
    primeiraAntiga: linha.public_first_viewed_at,
    ultimaAntiga: linha.public_last_viewed_at,
    pedidoEm: (pedido.data as { created_at?: string } | null)?.created_at ?? null,
    clienteNome: dadosDoCliente?.name || dadosDoCliente?.company_name || null,
    clienteWhatsapp: dadosDoCliente?.whatsapp || null,
  };

  const agora = new Date();
  const lidas = ((aberturas.data ?? []) as AberturaGuardada[]).reverse();
  const leitoresDaEquipe = ((equipe.data ?? []) as Array<{ viewer_key: string }>).map((item) => item.viewer_key);
  const { doCliente, equipe: daEquipe } = separarDaEquipe(lidas, leitoresDaEquipe);
  const visitas = montarVisitas(doCliente, orcamento.pedidoEm);
  const aparelhos = agruparAparelhos(doCliente, visitas);
  const resumo = resumir({ doCliente, daEquipe, visitas, aparelhos, orcamento, agora: agora.getTime() });

  const aberto = Boolean(resumo.primeira);
  const desde = new Date(aberto ? String(resumo.primeira) : orcamento.criadoEm).getTime();
  const diasDesde = Math.max(0, (agora.getTime() - desde) / 86_400_000);
  const decidido = Boolean(orcamento.pedidoEm) || ["approved", "rejected", "production", "delivered"].includes(String(orcamento.status));
  let respostaDaChance: unknown = null;
  if (!decidido && !orcamento.listaDePrecos) {
    const { data, error } = await admin.rpc("chance_de_fechar_orcamento", {
      p_orcamento: id,
      p_aberto: aberto,
      p_dias: pontosDaConsulta(diasDesde, aberto),
    });
    if (error) console.error("métricas do link: chance", error.message);
    else respostaDaChance = data;
  }
  const chance = montarChance({
    status: orcamento.status,
    pedidoEm: orcamento.pedidoEm,
    listaDePrecos: orcamento.listaDePrecos,
    aberto,
    diasDesde,
    resposta: respostaDaChance,
  });

  const listaDeEnvios: EnvioDoLink[] = ((envios.data ?? []) as Array<{ channel: string; created_at: string }>).map((envio) => ({
    canal: envio.channel === "whatsapp" ? "whatsapp" : "link",
    em: envio.created_at,
  }));

  const resposta: MetricasDoLink = {
    geradoEm: agora.toISOString(),
    orcamento: {
      numero: orcamento.numero,
      status: orcamento.status,
      criadoEm: orcamento.criadoEm,
      linkAtivo: orcamento.linkAtivo,
      linkVenceEm: orcamento.linkVenceEm,
      validade: orcamento.validade,
      pedidoEm: orcamento.pedidoEm,
      listaDePrecos: orcamento.listaDePrecos,
      cliente: { nome: orcamento.clienteNome, whatsapp: orcamento.clienteWhatsapp },
    },
    resumo,
    visitas,
    aparelhos,
    equipe: {
      aberturas: daEquipe.length,
      /* "Marcado à mão" é o que dá para desfazer; a marca do navegador do
         painel vem do próprio navegador e não tem o que desfazer. */
      aparelhos: agruparAparelhos(daEquipe).map((aparelho) => ({
        ...aparelho,
        marcadoAMao: daEquipe.some((item) => chaveDoAparelho(item) === aparelho.id && !item.is_team),
      })),
    },
    envios: listaDeEnvios,
    chance,
    sinais: sinaisDoLink(resumo, orcamento, agora.getTime()),
    passos: passosDoLink({
      resumo,
      orcamento,
      chance,
      ultimoEnvio: listaDeEnvios.length ? listaDeEnvios[listaDeEnvios.length - 1].em : null,
      agora: agora.getTime(),
      hoje: hojeEmSaoPaulo(agora),
    }),
  };
  return NextResponse.json(resposta, { headers: { "Cache-Control": "no-store" } });
}

export async function POST(req: Request, contexto: { params: Promise<{ id: string }> }) {
  const { id } = await contexto.params;
  if (!UUID.test(id)) return NextResponse.json({ error: "Orçamento inválido." }, { status: 400 });
  const corpo = (await req.json().catch(() => null)) as Record<string, unknown> | null;
  const acao = typeof corpo?.acao === "string" ? corpo.acao : "";

  if (acao === "envio") {
    const acesso = await orcamentoDaEmpresa(id, "quotes.read");
    if ("erro" in acesso) return acesso.erro;
    const canal = corpo?.canal === "whatsapp" ? "whatsapp" : "link";
    const admin = createAdminClient();
    /* O mesmo botão clicado várias vezes seguidas é um envio só. */
    const umMinutoAtras = new Date(Date.now() - 60_000).toISOString();
    const { data: recente } = await admin
      .from("quote_link_shares")
      .select("id")
      .eq("quote_id", id)
      .eq("channel", canal)
      .gte("created_at", umMinutoAtras)
      .limit(1);
    if (!recente?.length) {
      const { error } = await admin
        .from("quote_link_shares")
        .insert({ quote_id: id, company_id: acesso.orcamento.user_id, user_id: acesso.user.id, channel: canal });
      if (error) return NextResponse.json({ error: "Não deu para registrar o envio." }, { status: 500 });
    }
    return NextResponse.json({ ok: true });
  }

  if (acao === "equipe") {
    const acesso = await orcamentoDaEmpresa(id, "quotes.write");
    if ("erro" in acesso) return acesso.erro;
    const pedidos = Array.isArray(corpo?.leitores) ? corpo.leitores.filter((item): item is string => typeof item === "string") : [];
    if (!pedidos.length || pedidos.length > 50) return NextResponse.json({ error: "Nenhum aparelho escolhido." }, { status: 400 });
    const admin = createAdminClient();
    /* Só navegadores que abriram este orçamento: a marca não vira um jeito de
       mexer em aberturas de outros. */
    const { data: abertos } = await admin.from("quote_link_views").select("viewer_key").eq("quote_id", id).in("viewer_key", pedidos);
    const leitores = [...new Set(((abertos ?? []) as Array<{ viewer_key: string }>).map((item) => item.viewer_key))];
    if (!leitores.length) return NextResponse.json({ error: "Esse aparelho não abriu este orçamento." }, { status: 400 });
    const empresa = acesso.orcamento.user_id;
    if (corpo?.marcar === false) {
      const { error } = await admin.from("quote_link_team_viewers").delete().eq("company_id", empresa).in("viewer_key", leitores);
      if (error) return NextResponse.json({ error: "Não deu para desfazer a marca." }, { status: 500 });
    } else {
      const { error } = await admin
        .from("quote_link_team_viewers")
        .upsert(
          leitores.map((leitor) => ({ company_id: empresa, viewer_key: leitor, created_by: acesso.user.id })),
          { onConflict: "company_id,viewer_key", ignoreDuplicates: true }
        );
      if (error) return NextResponse.json({ error: "Não deu para marcar o aparelho." }, { status: 500 });
    }
    return NextResponse.json({ ok: true });
  }

  return NextResponse.json({ error: "Ação desconhecida." }, { status: 400 });
}
