import type { SupabaseClient } from "@supabase/supabase-js";
import { dataNoFuso, expedienteValido, segundosUteis, type Expediente } from "@/lib/orders/expediente";
import { montarFichasDoPedido, type ItemDoPedidoParaFicha } from "@/lib/orders/ficha-do-pedido.server";
import {
  capacidadeDaFila,
  compararPrevistoComReal,
  ratearPeloPrevisto,
  type CapacidadeDaFila,
  type LinhaDeTempo,
  type ProdutoPrevistoXReal,
} from "./tempo-de-producao";

/**
 * As consultas por trás de `tempo-de-producao.ts`: junta eventos, itens e
 * fichas do banco e entrega as linhas que a conta pura compara.
 *
 * Duas rotas usam isto — a saúde da precificação e a fila do kanban — e a
 * montagem vive aqui para o "previsto" ser o mesmo número nas duas: o da
 * receita congelada na venda quando existe, o do cadastro quando não.
 */

const SELECT_ITEM =
  "id, order_id, product_id, title, description, quantity, unit_type, width_m, height_m, length_m, selected_options, production_snapshot, production_minutes, products(sku, barcode, production_templates)";

type ItemComPedido = ItemDoPedidoParaFicha & { order_id: string };

type EventoBruto = {
  kind: "item" | "cronometro";
  order_id: string;
  order_item_id: string | null;
  started_at: string;
  ended_at: string | null;
  source: string | null;
};

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

/* Itens em blocos: a lista de ids viaja na URL. */
async function carregarItens(admin: SupabaseClient, orderIds: string[]) {
  const itens: ItemComPedido[] = [];
  for (let i = 0; i < orderIds.length; i += 150) {
    const bloco = orderIds.slice(i, i + 150);
    const { data, error } = await admin.from("order_items").select(SELECT_ITEM).in("order_id", bloco);
    if (error) return { itens, erro: error.message };
    itens.push(...((data || []) as unknown as ItemComPedido[]));
  }
  return { itens, erro: null as string | null };
}

async function expedienteDaEmpresa(admin: SupabaseClient, empresa: string): Promise<Expediente> {
  const { data } = await admin.from("profiles").select("production_work_schedule").eq("id", empresa).maybeSingle();
  return expedienteValido(data?.production_work_schedule);
}

export type PrevistoXReal = {
  porProduto: ProdutoPrevistoXReal[];
  cobertura: {
    pedidosComReal: number;
    porApontamento: number;
    porCronometro: number;
    itensComReal: number;
    aproximados: number;
  };
};

const VAZIO: PrevistoXReal = {
  porProduto: [],
  cobertura: { pedidosComReal: 0, porApontamento: 0, porCronometro: 0, itensComReal: 0, aproximados: 0 },
};

/**
 * Previsto × real dos pedidos com tempo medido desde `desdeIso`.
 *
 * O apontamento por item é a fonte boa. Na falta dele, o tempo útil do
 * cronômetro do pedido — só de pedido já concluído, senão o real é parcial —
 * rateado entre os itens pelo previsto de cada um.
 */
export async function previstoXRealDoPeriodo(
  admin: SupabaseClient,
  donos: string[],
  empresa: string,
  desdeIso: string
): Promise<{ dados: PrevistoXReal; erro: string | null }> {
  const expediente = await expedienteDaEmpresa(admin, empresa);

  const { data: eventosData, error: eventosErro } = await admin
    .from("order_production_events")
    .select("kind, order_id, order_item_id, started_at, ended_at, source")
    .in("user_id", donos)
    .in("kind", ["item", "cronometro"])
    .not("ended_at", "is", null)
    .gte("started_at", desdeIso)
    .order("started_at", { ascending: true })
    .limit(10000);
  if (eventosErro) return { dados: VAZIO, erro: eventosErro.message };

  const eventos = (eventosData || []) as EventoBruto[];
  const orderIds = [...new Set(eventos.map((e) => e.order_id))];
  if (orderIds.length === 0) return { dados: VAZIO, erro: null };

  const concluidos = new Set<string>();
  for (let i = 0; i < orderIds.length; i += 150) {
    const { data, error } = await admin
      .from("orders")
      .select("id, status, production_finished_at")
      .in("id", orderIds.slice(i, i + 150));
    if (error) return { dados: VAZIO, erro: error.message };
    for (const o of (data || []) as { id: string; status: string; production_finished_at: string | null }[]) {
      if (o.production_finished_at || o.status === "finished" || o.status === "delivered") concluidos.add(o.id);
    }
  }

  const { itens, erro: itensErro } = await carregarItens(admin, orderIds);
  if (itensErro) return { dados: VAZIO, erro: itensErro };

  const { fichas, error: fichasErro } = await montarFichasDoPedido(admin, itens, donos);
  if (fichasErro) return { dados: VAZIO, erro: fichasErro };
  const previstoPorItem = new Map(fichas.map((f) => [f.itemId, f.ficha.minutosTotais]));

  const productIds = [...new Set(itens.map((i) => i.product_id).filter((v): v is string => Boolean(v)))];
  const nomes = new Map<string, string>();
  for (let i = 0; i < productIds.length; i += 200) {
    const { data } = await admin.from("products").select("id, name").in("id", productIds.slice(i, i + 200));
    for (const p of (data || []) as { id: string; name: string | null }[]) nomes.set(p.id, p.name || "Produto");
  }

  /* O real por item, do apontamento. */
  const realDoItem = new Map<string, number>();
  const pedidosComApontamento = new Set<string>();
  for (const e of eventos) {
    if (e.kind !== "item" || !e.order_item_id || !e.ended_at) continue;
    const min = (Date.parse(e.ended_at) - Date.parse(e.started_at)) / 60000;
    if (!(min > 0)) continue;
    realDoItem.set(e.order_item_id, duasCasas((realDoItem.get(e.order_item_id) ?? 0) + min));
    pedidosComApontamento.add(e.order_id);
  }

  /* O real por pedido, do cronômetro cortado pelo expediente. As sessões
     reconstruídas do histórico ficam de fora: sem pausas, nem o expediente
     as salva — um pedido que dormiu nove dias "em produção" diria que a
     ficha erra por 120×. */
  const utilDoPedido = new Map<string, { minutos: number; aproximado: boolean }>();
  for (const e of eventos) {
    if (e.kind !== "cronometro" || !e.ended_at || !concluidos.has(e.order_id)) continue;
    if (e.source === "historico") continue;
    const seg = segundosUteis(Date.parse(e.started_at), Date.parse(e.ended_at), expediente);
    const atual = utilDoPedido.get(e.order_id) ?? { minutos: 0, aproximado: false };
    atual.minutos = duasCasas(atual.minutos + seg / 60);
    if (e.source === "historico") atual.aproximado = true;
    utilDoPedido.set(e.order_id, atual);
  }

  const itensPorPedido = new Map<string, ItemComPedido[]>();
  for (const item of itens) {
    const lista = itensPorPedido.get(item.order_id) ?? [];
    lista.push(item);
    itensPorPedido.set(item.order_id, lista);
  }

  const linhas: LinhaDeTempo[] = [];
  const pedidosComReal = new Set<string>();
  let porCronometro = 0;
  let aproximados = 0;

  for (const [orderId, doPedido] of itensPorPedido) {
    const comProduto = doPedido.filter((i) => i.product_id);
    const util = utilDoPedido.get(orderId);
    const rateio =
      util && util.minutos > 0
        ? ratearPeloPrevisto(
            util.minutos,
            comProduto.map((i) => ({ id: i.id, previsto: previstoPorItem.get(i.id) ?? 0 }))
          )
        : null;
    let usouCronometro = false;

    for (const item of comProduto) {
      const previsto = previstoPorItem.get(item.id) ?? 0;
      if (previsto <= 0) continue;
      const apontado = realDoItem.get(item.id) ?? 0;
      const base = {
        productId: item.product_id as string,
        nome: nomes.get(item.product_id as string) || item.title || "Produto",
        orderId,
        itemId: item.id,
        previstoMin: previsto,
      };
      if (apontado > 0) {
        linhas.push({ ...base, realMin: apontado, fonte: "item" });
        pedidosComReal.add(orderId);
      } else if (rateio && (rateio[item.id] ?? 0) > 0) {
        linhas.push({ ...base, realMin: rateio[item.id], fonte: "cronometro", aproximado: util?.aproximado });
        pedidosComReal.add(orderId);
        usouCronometro = true;
        if (util?.aproximado) aproximados += 1;
      }
    }
    if (usouCronometro) porCronometro += 1;
  }

  return {
    dados: {
      porProduto: compararPrevistoComReal(linhas),
      cobertura: {
        pedidosComReal: pedidosComReal.size,
        porApontamento: [...pedidosComReal].filter((id) => pedidosComApontamento.has(id)).length,
        porCronometro,
        itensComReal: linhas.length,
        aproximados,
      },
    },
    erro: null,
  };
}

export type FilaDeProducao = {
  pedidos: number;
  aguardando: number;
  emProducao: number;
  itens: number;
  itensSemFicha: number;
  minutosPrevistos: number;
  minutosAguardando: number;
  minutosEmProducao: number;
};

/**
 * A fila: os minutos previstos de tudo que ainda não saiu da produção, e o
 * quanto disso cabe nas horas produtivas do mês.
 */
export async function filaDeProducao(
  admin: SupabaseClient,
  donos: string[],
  empresa: string
): Promise<{ fila: FilaDeProducao; capacidade: CapacidadeDaFila; expediente: Expediente; erro: string | null }> {
  const expediente = await expedienteDaEmpresa(admin, empresa);
  const hoje = dataNoFuso(Date.now(), expediente.fuso);

  const { data: settings } = await admin
    .from("company_pricing_settings")
    .select("productive_hours_month")
    .eq("company_id", empresa)
    .maybeSingle();
  const horasMes = Number(settings?.productive_hours_month ?? 0) || null;

  const vazia: FilaDeProducao = {
    pedidos: 0, aguardando: 0, emProducao: 0, itens: 0, itensSemFicha: 0,
    minutosPrevistos: 0, minutosAguardando: 0, minutosEmProducao: 0,
  };
  const capacidadeDe = (fila: FilaDeProducao) =>
    capacidadeDaFila({
      minutosFila: fila.minutosPrevistos,
      pedidos: fila.pedidos,
      horasProdutivasMes: horasMes,
      diasDeExpediente: expediente.dias,
      hoje,
    });

  const { data: abertosData, error: abertosErro } = await admin
    .from("orders")
    .select("id, status")
    .in("user_id", donos)
    .in("status", ["pending_production", "in_production"])
    .limit(2000);
  if (abertosErro) return { fila: vazia, capacidade: capacidadeDe(vazia), expediente, erro: abertosErro.message };

  const abertos = (abertosData || []) as { id: string; status: string }[];
  if (abertos.length === 0) return { fila: vazia, capacidade: capacidadeDe(vazia), expediente, erro: null };

  const { itens, erro: itensErro } = await carregarItens(admin, abertos.map((o) => o.id));
  if (itensErro) return { fila: vazia, capacidade: capacidadeDe(vazia), expediente, erro: itensErro };

  const { fichas, error: fichasErro } = await montarFichasDoPedido(admin, itens, donos);
  if (fichasErro) return { fila: vazia, capacidade: capacidadeDe(vazia), expediente, erro: fichasErro };

  const statusDoPedido = new Map(abertos.map((o) => [o.id, o.status]));
  const previstoPorItem = new Map(fichas.map((f) => [f.itemId, f.ficha.minutosTotais]));

  const fila: FilaDeProducao = {
    pedidos: abertos.length,
    aguardando: abertos.filter((o) => o.status === "pending_production").length,
    emProducao: abertos.filter((o) => o.status === "in_production").length,
    itens: itens.length,
    itensSemFicha: 0,
    minutosPrevistos: 0,
    minutosAguardando: 0,
    minutosEmProducao: 0,
  };
  for (const item of itens) {
    const previsto = previstoPorItem.get(item.id) ?? 0;
    if (previsto <= 0) {
      fila.itensSemFicha += 1;
      continue;
    }
    fila.minutosPrevistos += previsto;
    if (statusDoPedido.get(item.order_id) === "in_production") fila.minutosEmProducao += previsto;
    else fila.minutosAguardando += previsto;
  }
  fila.minutosPrevistos = duasCasas(fila.minutosPrevistos);
  fila.minutosAguardando = duasCasas(fila.minutosAguardando);
  fila.minutosEmProducao = duasCasas(fila.minutosEmProducao);

  return { fila, capacidade: capacidadeDe(fila), expediente, erro: null };
}
