import { NextResponse } from "next/server";
import { createAdminClient, createClient } from "@/lib/supabase-server";
import { canCompany, getCompanyContext } from "@/lib/company-context";
import { medidaDaBase, type BaseDeConsumo } from "@/lib/precificacao/composicao";
import { curvaABC, dividirCusto, progressoDoMes } from "@/lib/precificacao/equilibrio";
import { filaDeProducao, previstoXRealDoPeriodo } from "@/lib/precificacao/tempo-de-producao.server";

/**
 * A saúde da precificação: margem real, pontos de equilíbrio e os avisos.
 *
 * Quatro perguntas que o sistema calculava aos pedaços e nunca respondia juntas:
 *
 *   1. Quanto sobra do que eu vendo? — margem de contribuição do período,
 *      por produto e por categoria, com a cobertura dita na cara: quando o
 *      custo veio congelado da venda e quando veio do cadastro atual.
 *   2. Quanto preciso vender para as contas fecharem? — os três pontos de
 *      equilíbrio: contábil (cobre as contas), financeiro (cobre o que sai do
 *      caixa, sem depreciação) e econômico (cobre as contas E o lucro que a
 *      pessoa quer).
 *   3. Como está O MÊS? — o termômetro: quanto do custo fixo o mês já pagou,
 *      se o ritmo alcança, previsão de lucro, e a curva ABC de quem está
 *      pagando a casa (cliente e vendedor).
 *   4. O que está pegando fogo? — insumo com estoque abaixo do mínimo e
 *      produto precificado com custo de insumo que já mudou.
 *
 * A margem de contribuição aqui é a do padrão do ramo (RKW): receita menos
 * MATÉRIA-PRIMA menos variáveis da venda. A mão de obra e a máquina ficam
 * DENTRO da contribuição — são a recuperação do custo fixo que a venda faz
 * (salários e energia estão na meta de fixos; subtraí-los da margem também
 * contava o mesmo real duas vezes e superestimava o equilíbrio). O custo de
 * cada item vendido é dividido em matéria-prima × mão de obra na proporção
 * da ficha do produto; sem ficha, tudo é matéria-prima — o lado conservador.
 *
 * Números sem cobertura conhecida mentem com confiança; cada bloco devolve
 * junto o quanto de dado real o sustenta.
 */

const duasCasas = (n: number) => Math.round(n * 100) / 100;

type ItemPedido = {
  id: string;
  order_id: string;
  product_id: string | null;
  quantity: number | null;
  unit_price: number | null;
  total_price: number | null;
  calculated_cost: number | null;
  unit_type: string | null;
  width_m: number | null;
  height_m: number | null;
};

/* Unidades de venda do item: peças, ou m² já vezes a quantidade. */
function unidadesDoItem(it: ItemPedido): number {
  const qtd = Math.max(0, Number(it.quantity) || 0);
  const w = Math.max(0, Number(it.width_m) || 0);
  const h = Math.max(0, Number(it.height_m) || 0);
  if (it.unit_type === "square_meter" && w > 0 && h > 0) return qtd * w * h;
  return qtd;
}

/**
 * Os dois alertas operacionais — insumo acabando e ficha com custo velho.
 *
 * Moram numa função própria porque têm dois clientes: o hub da precificação
 * (que só quer os alertas, via ?alertas=1) e o relatório completo. A conta do
 * custo defasado recalcula a ficha inteira — materiais E processos com a hora
 * atual de máquina/cargo — e deixa de fora, em vez de chutar, o produto sem
 * medida de referência ou sem hora atual conhecida.
 */
async function alertasDaSaude(
  admin: ReturnType<typeof createAdminClient>,
  empresa: string,
  donos: string[]
) {
  const [estoqueR, fichasR] = await Promise.all([
    admin
      .from("materials")
      .select("id,name,unit,current_stock,min_stock_alert")
      .eq("user_id", empresa)
      .eq("track_stock", true),
    admin
      .from("products")
      .select("id,name,cost_total,estimated_base_cost,unit_type,pricing_reference_width_m,pricing_reference_height_m,product_materials(consumption_qty,waste_pct,consumption_base,materials(cost_per_unit)),product_processes(minutes,setup_minutes,consumption_base,machines!machine_id(hourly_cost),labor_roles!role_id(hourly_rate))")
      .in("user_id", donos)
      .neq("product_kind", "digital")
      .eq("active", true)
      .limit(500),
  ]);

  const estoqueBaixo = (estoqueR.data ?? [])
    .filter((m) => {
      const saldo = Number(m.current_stock ?? 0);
      const minimo = m.min_stock_alert != null ? Number(m.min_stock_alert) : null;
      return saldo <= 0 || (minimo != null && saldo < minimo);
    })
    .map((m) => ({
      id: m.id,
      nome: m.name,
      unidade: m.unit,
      saldo: Number(m.current_stock ?? 0),
      minimo: m.min_stock_alert != null ? Number(m.min_stock_alert) : null,
    }))
    .sort((a, b) => a.saldo - b.saldo)
    .slice(0, 20);

  const custoDefasado: { id: string; nome: string; custoGravado: number; custoAtual: number; variacaoPct: number }[] = [];
  for (const p of fichasR.data ?? []) {
    const custoGravado = Number(p.cost_total ?? 0) || Number(p.estimated_base_cost ?? 0);
    if (custoGravado <= 0) continue;

    const refW = Number(p.pricing_reference_width_m ?? 0);
    const refH = Number(p.pricing_reference_height_m ?? 0);
    const areaRef = refW * refH;

    let total = 0;
    let incompleto = false;
    for (const linha of (p.product_materials ?? []) as unknown as {
      consumption_qty: number | null; waste_pct: number | null; consumption_base: string | null;
      /* O client tipa a relação aninhada como lista; em runtime um FK
         many-to-one volta objeto. Aceitar os dois é mais barato que brigar. */
      materials: { cost_per_unit: number | null } | { cost_per_unit: number | null }[] | null;
    }[]) {
      const base = (linha.consumption_base ?? "por_peca") as BaseDeConsumo;
      const insumo = Array.isArray(linha.materials) ? linha.materials[0] : linha.materials;
      const custoInsumo = Number(insumo?.cost_per_unit ?? 0);
      const consumo = Number(linha.consumption_qty ?? 0);
      if (custoInsumo <= 0 || consumo <= 0) continue;
      const medida = medidaDaBase(base, refW, refH);
      if (base !== "por_peca" && medida <= 0) { incompleto = true; break; }
      total += consumo * (1 + Number(linha.waste_pct ?? 0) / 100) * custoInsumo * medida;
    }

    /* Os processos, na mesma fórmula com que a ficha os gravou:
       minutos/60 × hora × medida, mais o preparo uma vez por peça. */
    for (const linha of (p.product_processes ?? []) as unknown as {
      minutes: number | null; setup_minutes: number | null; consumption_base: string | null;
      machines: { hourly_cost: number | null } | { hourly_cost: number | null }[] | null;
      labor_roles: { hourly_rate: number | null } | { hourly_rate: number | null }[] | null;
    }[]) {
      if (incompleto) break;
      const minutos = Number(linha.minutes ?? 0);
      const setup = Number(linha.setup_minutes ?? 0);
      if (minutos <= 0 && setup <= 0) continue;
      const maquina = Array.isArray(linha.machines) ? linha.machines[0] : linha.machines;
      const cargo = Array.isArray(linha.labor_roles) ? linha.labor_roles[0] : linha.labor_roles;
      const horaAtual = Number(maquina?.hourly_cost ?? 0) || Number(cargo?.hourly_rate ?? 0);
      if (horaAtual <= 0) { incompleto = true; break; }
      const base = (linha.consumption_base ?? "por_peca") as BaseDeConsumo;
      const medida = medidaDaBase(base, refW, refH);
      if (base !== "por_peca" && medida <= 0) { incompleto = true; break; }
      total += (minutos / 60) * horaAtual * medida + (setup / 60) * horaAtual;
    }
    if (incompleto || total <= 0) continue;

    /* Na unidade de venda: por m² quando o produto é medido com referência. */
    const custoAtual = p.unit_type === "square_meter" && areaRef > 0 ? total / areaRef : total;
    const variacao = ((custoAtual - custoGravado) / custoGravado) * 100;
    if (Math.abs(variacao) >= 5) {
      custoDefasado.push({
        id: p.id,
        nome: String(p.name ?? ""),
        custoGravado: duasCasas(custoGravado),
        custoAtual: duasCasas(custoAtual),
        variacaoPct: duasCasas(variacao),
      });
    }
  }
  custoDefasado.sort((a, b) => Math.abs(b.variacaoPct) - Math.abs(a.variacaoPct));

  return { estoqueBaixo, custoDefasado: custoDefasado.slice(0, 20) };
}

export async function GET(req: Request) {
  const supabase = await createClient();
  const {
    data: { user },
  } = await supabase.auth.getUser();
  if (!user) return NextResponse.json({ error: "Não autenticado." }, { status: 401 });

  const ctx = await getCompanyContext(supabase, user);
  if (!canCompany(ctx, user.id, "pricing.read")) {
    return NextResponse.json({ error: "Sem permissão." }, { status: 403 });
  }

  const parametros = new URL(req.url).searchParams;
  const admin = createAdminClient();
  const empresa = ctx.companyId;
  const donos = ctx.tenantIds;

  /* O modo leve: só os alertas, para o hub da precificação — sem carregar
     seis meses de pedidos para mostrar dois cartões. */
  if (parametros.get("alertas") === "1") {
    return NextResponse.json({ ok: true, alertas: await alertasDaSaude(admin, empresa, donos) });
  }

  const dias = Math.min(365, Math.max(7, Number(parametros.get("dias")) || 30));
  const desde = new Date(Date.now() - dias * 24 * 60 * 60 * 1000).toISOString();

  const [pedidosR, fixoR, variaveisR, settingsR, maquinasR] = await Promise.all([
    admin
      .from("orders")
      .select("id,created_at")
      .in("user_id", donos)
      .neq("status", "canceled")
      .gte("created_at", desde),
    admin.rpc("custo_fixo_mensal", { p_company_id: empresa }),
    admin.rpc("custo_variavel_da_venda", { p_company_id: empresa }),
    admin
      .from("company_pricing_settings")
      .select("desired_profit_monthly,monthly_sales_goal")
      .eq("company_id", empresa)
      .maybeSingle(),
    admin
      .from("machines")
      .select("productive_hours_month,acquisition_value,residual_value,useful_life_years,is_active")
      .eq("user_id", empresa)
      .eq("is_active", true),
  ]);

  if (pedidosR.error) return NextResponse.json({ error: pedidosR.error.message }, { status: 400 });

  const idsDePedidos = (pedidosR.data ?? []).map((p) => p.id);

  /* Itens em blocos: um período cheio pode ter milhares de pedidos, e a lista
     de ids viaja na URL. */
  const carregarItens = async (ids: string[]): Promise<ItemPedido[] | { erro: string }> => {
    const lista: ItemPedido[] = [];
    for (let i = 0; i < ids.length; i += 150) {
      const bloco = ids.slice(i, i + 150);
      const { data, error } = await admin
        .from("order_items")
        .select("id,order_id,product_id,quantity,unit_price,total_price,calculated_cost,unit_type,width_m,height_m")
        .in("order_id", bloco);
      if (error) return { erro: error.message };
      lista.push(...((data ?? []) as ItemPedido[]));
    }
    return lista;
  };

  const itensOuErro = await carregarItens(idsDePedidos);
  if ("erro" in itensOuErro) return NextResponse.json({ error: itensOuErro.erro }, { status: 400 });
  const itens = itensOuErro;

  /* O MÊS CALENDÁRIO, separado da janela: o termômetro zera todo dia 1º,
     como as contas da empresa. Os cinco meses anteriores viajam juntos para
     o histórico — o "outros meses" que mostra a trajetória. */
  const agora = new Date();
  const competenciaDe = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
  const competenciaAtual = competenciaDe(agora);
  const inicioDoHistorico = new Date(agora.getFullYear(), agora.getMonth() - 5, 1);

  const { data: pedidosDesdeR, error: pedidosDesdeErro } = await admin
    .from("orders")
    .select("id,created_at,client_id,primary_seller_user_id")
    .in("user_id", donos)
    .neq("status", "canceled")
    .gte("created_at", inicioDoHistorico.toISOString());
  if (pedidosDesdeErro) return NextResponse.json({ error: pedidosDesdeErro.message }, { status: 400 });
  const pedidosDesde = pedidosDesdeR ?? [];

  const itensDesdeOuErro = await carregarItens(pedidosDesde.map((p) => p.id));
  if ("erro" in itensDesdeOuErro) return NextResponse.json({ error: itensDesdeOuErro.erro }, { status: 400 });
  const itensDesde = itensDesdeOuErro;

  const idsDeProdutos = [
    ...new Set(
      [...itens, ...itensDesde].map((i) => i.product_id).filter((v): v is string => Boolean(v))
    ),
  ];

  const [produtosR, processosR, fichaMateriaisR, fichaProcessosR] = await Promise.all([
    idsDeProdutos.length
      ? admin
          .from("products")
          .select("id,name,category_id,cost_total,estimated_base_cost")
          .in("id", idsDeProdutos)
      : Promise.resolve({ data: [], error: null }),
    idsDeProdutos.length
      ? admin
          .from("product_processes")
          .select("product_id,minutes,setup_minutes,consumption_base")
          .eq("kind", "MACHINE")
          .in("product_id", idsDeProdutos)
      : Promise.resolve({ data: [], error: null }),
    /* As duas metades da ficha, para dividir o custo de cada item vendido em
       matéria-prima × mão de obra na proporção certa. */
    idsDeProdutos.length
      ? admin.from("product_materials").select("product_id,cost_total").in("product_id", idsDeProdutos)
      : Promise.resolve({ data: [], error: null }),
    idsDeProdutos.length
      ? admin.from("product_processes").select("product_id,cost_total").in("product_id", idsDeProdutos)
      : Promise.resolve({ data: [], error: null }),
  ]);

  const fichaDoProduto = new Map<string, { materiais: number; processos: number }>();
  for (const linha of fichaMateriaisR.data ?? []) {
    const atual = fichaDoProduto.get(linha.product_id) ?? { materiais: 0, processos: 0 };
    atual.materiais += Number(linha.cost_total ?? 0) || 0;
    fichaDoProduto.set(linha.product_id, atual);
  }
  for (const linha of fichaProcessosR.data ?? []) {
    const atual = fichaDoProduto.get(linha.product_id) ?? { materiais: 0, processos: 0 };
    atual.processos += Number(linha.cost_total ?? 0) || 0;
    fichaDoProduto.set(linha.product_id, atual);
  }

  const produto = new Map(
    (produtosR.data ?? []).map((p) => [p.id, {
      nome: String(p.name ?? ""),
      categoria: p.category_id as string | null,
      custo: Number(p.cost_total ?? 0) || Number(p.estimated_base_cost ?? 0) || 0,
    }])
  );
  const processosDoProduto = new Map<string, { minutos: number; setup: number; base: BaseDeConsumo }[]>();
  for (const pp of processosR.data ?? []) {
    const lista = processosDoProduto.get(pp.product_id) ?? [];
    lista.push({
      minutos: Number(pp.minutes ?? 0) || 0,
      setup: Number(pp.setup_minutes ?? 0) || 0,
      base: (pp.consumption_base ?? "por_peca") as BaseDeConsumo,
    });
    processosDoProduto.set(pp.product_id, lista);
  }

  const variaveisPct = Number((variaveisR.data as { total_pct?: number } | null)?.total_pct ?? 0);

  /* A leitura de um item vendido: receita, custo (congelado na venda ou do
     cadastro atual) e a divisão matéria-prima × mão de obra pela ficha. É a
     mesma conta para a janela e para o mês — conta repetida é conta que
     diverge. */
  const lerItem = (it: ItemPedido) => {
    const unidades = unidadesDoItem(it);
    const receitaItem =
      Number(it.total_price ?? 0) > 0
        ? Number(it.total_price)
        : Math.max(0, Number(it.unit_price) || 0) * unidades;

    const prod = it.product_id ? produto.get(it.product_id) : undefined;
    let custoItem: number | null = null;
    let congelado = false;
    if (it.calculated_cost != null && Number(it.calculated_cost) > 0) {
      custoItem = Number(it.calculated_cost) * unidades;
      congelado = true;
    } else if (prod && prod.custo > 0) {
      custoItem = prod.custo * unidades;
    }

    const ficha = it.product_id ? fichaDoProduto.get(it.product_id) : undefined;
    const partes = dividirCusto(custoItem ?? 0, ficha?.materiais ?? 0, ficha?.processos ?? 0);

    return { unidades, receitaItem, custoItem, congelado, prod, ...partes };
  };

  /* --------------------------------------------------------------------- */
  /* Margem de contribuição do período                                     */
  /* --------------------------------------------------------------------- */
  let receita = 0;
  let custoDireto = 0;
  let materiaPrima = 0;
  let maoDeObra = 0;
  let receitaComCusto = 0;
  let receitaCongelada = 0;
  let receitaComFichaDeMaquina = 0;
  let minutosDeMaquina = 0;

  const porProduto = new Map<string, { nome: string; categoria: string | null; receita: number; materiais: number; comCusto: boolean }>();

  for (const it of itens) {
    const { unidades, receitaItem, custoItem, congelado, prod, materiais } = lerItem(it);
    if (receitaItem <= 0 && unidades <= 0) continue;

    receita += receitaItem;
    if (congelado) receitaCongelada += receitaItem;
    if (custoItem != null) {
      custoDireto += custoItem;
      materiaPrima += materiais;
      maoDeObra += custoItem - materiais;
      receitaComCusto += receitaItem;
    }

    if (it.product_id && prod) {
      const chave = it.product_id;
      const atual = porProduto.get(chave) ?? { nome: prod.nome, categoria: prod.categoria, receita: 0, materiais: 0, comCusto: false };
      atual.receita += receitaItem;
      if (custoItem != null) { atual.materiais += materiais; atual.comCusto = true; }
      porProduto.set(chave, atual);

      const procs = processosDoProduto.get(it.product_id);
      if (procs && procs.length > 0) {
        const qtd = Math.max(0, Number(it.quantity) || 0);
        const w = Math.max(0, Number(it.width_m) || 0);
        const h = Math.max(0, Number(it.height_m) || 0);
        let min = 0;
        for (const p of procs) {
          const medida = medidaDaBase(p.base, w, h);
          if (p.base !== "por_peca" && medida <= 0) continue;
          min += p.minutos * medida * qtd + p.setup;
        }
        if (min > 0) {
          minutosDeMaquina += min;
          receitaComFichaDeMaquina += receitaItem;
        }
      }
    }
  }

  const variaveisValor = duasCasas((receita * variaveisPct) / 100);
  /* Contribuição no padrão do ramo: só matéria-prima e variáveis saem. A mão
     de obra fica dentro — é ela que paga o fixo. */
  const margemContribuicao = duasCasas(receita - materiaPrima - variaveisValor);
  const mcPct = receita > 0 ? duasCasas((margemContribuicao / receita) * 100) : 0;

  /* --------------------------------------------------------------------- */
  /* Pontos de equilíbrio (mensais)                                        */
  /* --------------------------------------------------------------------- */
  const fixo = Number((fixoR.data as { total_mensal?: number } | null)?.total_mensal ?? 0);
  const lucroDesejado = Number(settingsR.data?.desired_profit_monthly ?? 0);
  const depreciacaoMensal = duasCasas(
    (maquinasR.data ?? []).reduce((acc, m) => {
      const aquisicao = Number(m.acquisition_value ?? 0);
      const residual = Number(m.residual_value ?? 0);
      const vida = Number(m.useful_life_years ?? 0);
      return vida > 0 && aquisicao > residual ? acc + (aquisicao - residual) / vida / 12 : acc;
    }, 0)
  );

  const pe = (alvo: number) => (mcPct > 0 && alvo > 0 ? duasCasas(alvo / (mcPct / 100)) : null);
  const contabil = pe(fixo);
  const financeiro = pe(Math.max(0, fixo - depreciacaoMensal));
  const economico = pe(fixo + Math.max(0, lucroDesejado));

  const receitaMensalizada = dias > 0 ? duasCasas((receita / dias) * 30) : 0;

  /* --------------------------------------------------------------------- */
  /* O mês, competência a competência: o atual e os cinco fechados, cada   */
  /* um com o dashboard inteiro — a aba Outros Meses expande sem nova      */
  /* viagem ao servidor.                                                   */
  /* --------------------------------------------------------------------- */
  const pedidoDoItem = new Map(pedidosDesde.map((p) => [p.id, p]));
  type BucketDoMes = {
    receita: number;
    cm: number;
    receitaComCusto: number;
    porPedido: Map<string, { receita: number; cm: number; custo: number; incompleto: boolean }>;
    porDia: Map<number, { receita: number; cm: number }>;
  };
  const porCompetencia = new Map<string, BucketDoMes>();

  for (const it of itensDesde) {
    const { unidades, receitaItem, custoItem, materiais } = lerItem(it);
    if (receitaItem <= 0 && unidades <= 0) continue;
    const pedido = pedidoDoItem.get(it.order_id);
    if (!pedido) continue;
    /* Item sem custo conhecido: contribuição = receita − variáveis, com a
       cobertura dita na resposta — melhor superestimar dizendo do que
       esconder a venda do termômetro. */
    const cmItem = receitaItem - materiais - (receitaItem * variaveisPct) / 100;

    const dataDoPedido = new Date(pedido.created_at);
    const competencia = competenciaDe(dataDoPedido);
    const bucket = porCompetencia.get(competencia) ?? {
      receita: 0, cm: 0, receitaComCusto: 0,
      porPedido: new Map(), porDia: new Map(),
    };
    bucket.receita += receitaItem;
    bucket.cm += cmItem;
    if (custoItem != null) bucket.receitaComCusto += receitaItem;

    const doPedido = bucket.porPedido.get(it.order_id) ?? { receita: 0, cm: 0, custo: 0, incompleto: false };
    doPedido.receita += receitaItem;
    doPedido.cm += cmItem;
    /* O lucro do pedido pede o custo INTEIRO conhecido: um item sem custo
       marca o pedido como incompleto, e o lucro dele vira "—" em vez de
       número otimista. */
    if (custoItem != null) doPedido.custo += custoItem;
    else if (receitaItem > 0) doPedido.incompleto = true;
    bucket.porPedido.set(it.order_id, doPedido);

    const dia = dataDoPedido.getDate();
    const doDia = bucket.porDia.get(dia) ?? { receita: 0, cm: 0 };
    doDia.receita += receitaItem;
    doDia.cm += cmItem;
    bucket.porDia.set(dia, doDia);

    porCompetencia.set(competencia, bucket);
  }

  /* A fotografia do fixo: cada mês fechado congela o custo fixo vigente na
     primeira consulta depois da virada — o lucro histórico usa a foto, como
     o ramo faz. Mês fechado antes da fotografia existir herda o valor de
     hoje (uma vez, e congela). */
  const competenciasFechadas: string[] = [];
  for (let i = 1; i <= 5; i++) {
    competenciasFechadas.push(competenciaDe(new Date(agora.getFullYear(), agora.getMonth() - i, 1)));
  }
  const { data: fotosR } = await admin
    .from("company_breakeven_snapshots")
    .select("competencia,fixed_monthly")
    .eq("company_id", empresa)
    .in("competencia", competenciasFechadas);
  const fotoDoMes = new Map((fotosR ?? []).map((f) => [f.competencia, Number(f.fixed_monthly ?? 0)]));
  const semFoto = competenciasFechadas.filter((c) => !fotoDoMes.has(c));
  if (semFoto.length > 0) {
    await admin.from("company_breakeven_snapshots").upsert(
      semFoto.map((competencia) => ({ company_id: empresa, competencia, fixed_monthly: fixo })),
      { onConflict: "company_id,competencia", ignoreDuplicates: true }
    );
    for (const c of semFoto) fotoDoMes.set(c, fixo);
  }

  const metaDeVendas = Number(settingsR.data?.monthly_sales_goal ?? 0);

  /* --------------------------------------------------------------------- */
  /* Curva ABC e o bloco completo de cada competência                      */
  /* --------------------------------------------------------------------- */
  /* O rateio de vendedores das vendas divididas: a contribuição segue o
     percentual de cada um; sem rateio, vale o vendedor principal inteiro. */
  const idsComVenda = [...porCompetencia.values()].flatMap((b) => [...b.porPedido.keys()]);
  const alocacoesR = idsComVenda.length
    ? await admin
        .from("sale_commission_allocations")
        .select("order_id,seller_user_id,allocation_percent")
        .in("order_id", idsComVenda)
    : { data: [] as { order_id: string; seller_user_id: string; allocation_percent: number | null }[] };
  const alocacoesPorPedido = new Map<string, { seller: string; pct: number }[]>();
  for (const a of alocacoesR.data ?? []) {
    if (!a.seller_user_id) continue;
    const lista = alocacoesPorPedido.get(a.order_id) ?? [];
    lista.push({ seller: a.seller_user_id, pct: Number(a.allocation_percent ?? 0) || 0 });
    alocacoesPorPedido.set(a.order_id, lista);
  }

  /* Nomes: cliente pelo cadastro, vendedor pelo e-mail do acesso — todos os
     citados no semestre, numa viagem só. */
  const idsDeClientes = [
    ...new Set(
      idsComVenda
        .map((id) => pedidoDoItem.get(id)?.client_id)
        .filter((v): v is string => Boolean(v))
    ),
  ];
  const idsDeVendedores = [
    ...new Set([
      ...idsComVenda
        .map((id) => pedidoDoItem.get(id)?.primary_seller_user_id)
        .filter((v): v is string => Boolean(v)),
      ...[...alocacoesPorPedido.values()].flat().map((a) => a.seller),
    ]),
  ];
  const [clientesR, vendedoresR] = await Promise.all([
    idsDeClientes.length
      ? admin.from("clients").select("id,name,company_name").in("id", idsDeClientes)
      : Promise.resolve({ data: [] as { id: string; name: string | null; company_name: string | null }[] }),
    idsDeVendedores.length
      ? admin.from("company_users").select("user_id,email").in("user_id", idsDeVendedores)
      : Promise.resolve({ data: [] as { user_id: string; email: string | null }[] }),
  ]);
  const nomeDoCliente = new Map(
    (clientesR.data ?? []).map((c) => [c.id, String(c.name || c.company_name || "Cliente")])
  );
  const nomeDoVendedor = new Map(
    (vendedoresR.data ?? []).map((v) => [v.user_id, String(v.email ?? "").split("@")[0] || "Vendedor"])
  );

  /* O dashboard de uma competência: termômetro, caminho do lucro e as duas
     curvas ABC. Mês fechado congela no último dia, com o fixo da foto. */
  const blocoDaCompetencia = (competencia: string) => {
    const bucket = porCompetencia.get(competencia) ?? {
      receita: 0, cm: 0, receitaComCusto: 0,
      porPedido: new Map<string, { receita: number; cm: number; custo: number; incompleto: boolean }>(),
      porDia: new Map<number, { receita: number; cm: number }>(),
    };
    const fechado = competencia !== competenciaAtual;
    const [ano, mesNumero] = competencia.split("-").map(Number);
    const diasNaCompetencia = new Date(ano, mesNumero, 0).getDate();
    const fixoDaCompetencia = fechado ? (fotoDoMes.get(competencia) ?? fixo) : fixo;

    const termometro = progressoDoMes({
      fixoMensal: fixoDaCompetencia,
      cmAcumulada: duasCasas(bucket.cm),
      receitaMes: duasCasas(bucket.receita),
      diaAtual: fechado ? diasNaCompetencia : agora.getDate(),
      diasNoMes: diasNaCompetencia,
      metaDeVendas,
    });

    const porCliente = new Map<string, { receita: number; cm: number; lucro: number; incompleto: boolean; pedidos: number }>();
    const porVendedor = new Map<string, { receita: number; cm: number; lucro: number; incompleto: boolean; pedidos: number }>();
    for (const [orderId, valores] of bucket.porPedido) {
      const pedido = pedidoDoItem.get(orderId);
      /* O lucro do pedido: receita menos o custo INTEIRO menos os variáveis
         — a régua da coluna Lucro do ranking, ao lado da MC. */
      const lucroDoPedido = valores.incompleto
        ? 0
        : valores.receita - valores.custo - (valores.receita * variaveisPct) / 100;

      const cliente = pedido?.client_id ?? "sem-cliente";
      const c = porCliente.get(cliente) ?? { receita: 0, cm: 0, lucro: 0, incompleto: false, pedidos: 0 };
      c.receita += valores.receita;
      c.cm += valores.cm;
      c.lucro += lucroDoPedido;
      c.incompleto = c.incompleto || valores.incompleto;
      c.pedidos += 1;
      porCliente.set(cliente, c);

      const fatias = alocacoesPorPedido.get(orderId)?.length
        ? alocacoesPorPedido.get(orderId)!
        : [{ seller: pedido?.primary_seller_user_id ?? "sem-vendedor", pct: 100 }];
      for (const fatia of fatias) {
        const v = porVendedor.get(fatia.seller) ?? { receita: 0, cm: 0, lucro: 0, incompleto: false, pedidos: 0 };
        v.receita += (valores.receita * fatia.pct) / 100;
        v.cm += (valores.cm * fatia.pct) / 100;
        v.lucro += (lucroDoPedido * fatia.pct) / 100;
        v.incompleto = v.incompleto || valores.incompleto;
        v.pedidos += 1;
        porVendedor.set(fatia.seller, v);
      }
    }

    const rankingClientes = curvaABC(
      [...porCliente.entries()].map(([id, c]) => ({
        nome: id === "sem-cliente" ? "Sem cliente" : (nomeDoCliente.get(id) ?? "Cliente"),
        receita: duasCasas(c.receita),
        cm: duasCasas(c.cm),
        lucro: c.incompleto ? null : duasCasas(c.lucro),
        pedidos: c.pedidos,
      }))
    ).slice(0, 10);
    const rankingVendedores = curvaABC(
      [...porVendedor.entries()].map(([id, v]) => ({
        nome: id === "sem-vendedor" ? "Sem vendedor" : (nomeDoVendedor.get(id) ?? "Vendedor principal"),
        receita: duasCasas(v.receita),
        cm: duasCasas(v.cm),
        lucro: v.incompleto ? null : duasCasas(v.lucro),
        pedidos: v.pedidos,
      }))
    ).slice(0, 10);

    return {
      competencia,
      fechado,
      diaAtual: fechado ? diasNaCompetencia : agora.getDate(),
      diasNoMes: diasNaCompetencia,
      pedidos: bucket.porPedido.size,
      receita: duasCasas(bucket.receita),
      cm: duasCasas(bucket.cm),
      fixoDoMes: duasCasas(fixoDaCompetencia),
      coberturaPct: bucket.receita > 0 ? duasCasas((bucket.receitaComCusto / bucket.receita) * 100) : 0,
      ...termometro,
      porDia: [...bucket.porDia.entries()]
        .map(([dia, v]) => ({ dia, receita: duasCasas(v.receita), cm: duasCasas(v.cm) }))
        .sort((a, b) => a.dia - b.dia),
      rankingClientes,
      rankingVendedores,
    };
  };

  const mesAtualBloco = blocoDaCompetencia(competenciaAtual);
  const outrosMeses = competenciasFechadas
    .filter((c) => porCompetencia.has(c))
    .map(blocoDaCompetencia);

  /* --------------------------------------------------------------------- */
  /* Capacidade: o equilíbrio cabe nas horas de máquina que existem?       */
  /* --------------------------------------------------------------------- */
  const horasDisponiveisMes = duasCasas(
    (maquinasR.data ?? []).reduce((acc, m) => acc + (Number(m.productive_hours_month ?? 0) || 0), 0)
  );
  const coberturaFichaPct = receita > 0 ? duasCasas((receitaComFichaDeMaquina / receita) * 100) : 0;
  const horasUsadas = duasCasas(minutosDeMaquina / 60);
  /* Abaixo de 30% de cobertura o número seria chute vestido de métrica. */
  const receitaPorHora =
    coberturaFichaPct >= 30 && horasUsadas > 0 ? receitaComFichaDeMaquina / horasUsadas : null;
  const horasNoEquilibrio =
    receitaPorHora && contabil ? duasCasas(contabil / receitaPorHora) : null;

  /* --------------------------------------------------------------------- */
  /* Alertas                                                               */
  /* --------------------------------------------------------------------- */
  const alertas = await alertasDaSaude(admin, empresa, donos);

  /* O tempo: a ficha confrontada com a bancada, e a fila contra o mês. */
  const [previstoXReal, filaR] = await Promise.all([
    previstoXRealDoPeriodo(admin, donos, empresa, desde),
    filaDeProducao(admin, donos, empresa),
  ]);

  /* A margem exibida por produto é a de CONTRIBUIÇÃO: receita − matéria-prima
     − variáveis. A parcela de mão de obra do custo fica dentro, porque ela
     paga o fixo — mesma régua do termômetro. */
  const listaProdutos = [...porProduto.entries()]
    .map(([id, p]) => ({
      id,
      nome: p.nome,
      categoria: p.categoria,
      receita: duasCasas(p.receita),
      materiais: p.comCusto ? duasCasas(p.materiais) : null,
      margemPct: p.comCusto && p.receita > 0
        ? duasCasas(((p.receita - p.materiais - (p.receita * variaveisPct) / 100) / p.receita) * 100)
        : null,
    }))
    .sort((a, b) => b.receita - a.receita);

  /* Nomes das categorias só para as que aparecem. */
  const idsDeCategorias = [...new Set(listaProdutos.map((p) => p.categoria).filter((v): v is string => Boolean(v)))];
  const categoriasR = idsDeCategorias.length
    ? await admin.from("categories").select("id,name").in("id", idsDeCategorias)
    : { data: [] as { id: string; name: string }[] };
  const nomeDaCategoria = new Map((categoriasR.data ?? []).map((c) => [c.id, c.name]));

  const porCategoria = new Map<string, { receita: number; materiais: number; comCusto: boolean }>();
  for (const p of listaProdutos) {
    const chave = p.categoria ? (nomeDaCategoria.get(p.categoria) ?? "Sem categoria") : "Sem categoria";
    const atual = porCategoria.get(chave) ?? { receita: 0, materiais: 0, comCusto: false };
    atual.receita += p.receita;
    if (p.materiais != null) { atual.materiais += p.materiais; atual.comCusto = true; }
    porCategoria.set(chave, atual);
  }

  return NextResponse.json({
    periodo: { dias, desde },
    vendas: {
      pedidos: idsDePedidos.length,
      receita: duasCasas(receita),
      custoDireto: duasCasas(custoDireto),
      materiaPrima: duasCasas(materiaPrima),
      maoDeObra: duasCasas(maoDeObra),
      variaveisPct,
      variaveisValor,
      margemContribuicao,
      mcPct,
      cobertura: {
        comCustoPct: receita > 0 ? duasCasas((receitaComCusto / receita) * 100) : 0,
        congeladaPct: receita > 0 ? duasCasas((receitaCongelada / receita) * 100) : 0,
      },
    },
    /* O termômetro do mês calendário — zera todo dia 1º. */
    mes: mesAtualBloco,
    /* Os meses fechados, cada um com o dashboard inteiro, prontos para a
       aba Outros Meses expandir sem nova consulta. */
    outrosMeses,
    porProduto: listaProdutos.slice(0, 15),
    porCategoria: [...porCategoria.entries()]
      .map(([nome, c]) => ({
        nome,
        receita: duasCasas(c.receita),
        margemPct: c.comCusto && c.receita > 0
          ? duasCasas(((c.receita - c.materiais - (c.receita * variaveisPct) / 100) / c.receita) * 100)
          : null,
      }))
      .sort((a, b) => b.receita - a.receita)
      .slice(0, 10),
    equilibrio: {
      fixoMensal: duasCasas(fixo),
      mesesComLancamento: Number((fixoR.data as { meses_com_lancamento?: number } | null)?.meses_com_lancamento ?? 0),
      depreciacaoMensal,
      lucroDesejado: duasCasas(lucroDesejado),
      mcPct,
      contabil,
      financeiro,
      economico,
      receitaMensalizada,
    },
    capacidade: {
      horasDisponiveisMes,
      horasUsadasPeriodo: horasUsadas,
      coberturaFichaPct,
      horasNoEquilibrio,
    },
    /* Previsto × real por produto e a fila contra a capacidade do mês. */
    tempo: {
      previstoXReal: previstoXReal.dados,
      fila: filaR.fila,
      capacidade: filaR.capacidade,
      expediente: filaR.expediente,
      erro: previstoXReal.erro ?? filaR.erro ?? null,
    },
    alertas,
  });
}

/** Salva as duas metas do mês: o lucro desejado (equilíbrio econômico) e a
 *  meta de vendas do comercial (a régua da gestão no termômetro). */
export async function POST(req: Request) {
  const supabase = await createClient();
  const {
    data: { user },
  } = await supabase.auth.getUser();
  if (!user) return NextResponse.json({ error: "Não autenticado." }, { status: 401 });

  const ctx = await getCompanyContext(supabase, user);
  if (!canCompany(ctx, user.id, "pricing.health.write")) {
    return NextResponse.json({ error: "Sem permissão." }, { status: 403 });
  }

  const body = (await req.json().catch(() => null)) as
    | { lucro_desejado?: unknown; meta_de_vendas?: unknown }
    | null;

  const atualizacao: Record<string, unknown> = { company_id: ctx.companyId };
  if (body && "lucro_desejado" in body) {
    const lucro = Number(body.lucro_desejado);
    if (!Number.isFinite(lucro) || lucro < 0) {
      return NextResponse.json({ error: "Informe um valor válido." }, { status: 400 });
    }
    atualizacao.desired_profit_monthly = lucro;
  }
  if (body && "meta_de_vendas" in body) {
    const meta = Number(body.meta_de_vendas);
    if (!Number.isFinite(meta) || meta < 0) {
      return NextResponse.json({ error: "Informe uma meta válida." }, { status: 400 });
    }
    /* Zero limpa a meta: o termômetro volta a usar só o equilíbrio. */
    atualizacao.monthly_sales_goal = meta > 0 ? meta : null;
  }
  if (Object.keys(atualizacao).length === 1) {
    return NextResponse.json({ error: "Nada para salvar." }, { status: 400 });
  }

  const admin = createAdminClient();
  const { error } = await admin
    .from("company_pricing_settings")
    .upsert(atualizacao, { onConflict: "company_id" });
  if (error) return NextResponse.json({ error: error.message }, { status: 400 });

  return NextResponse.json({ ok: true });
}
