import type { SupabaseClient } from "@supabase/supabase-js";
import type { BaseDeConsumo } from "@/lib/precificacao/composicao";
import {
  avisosDaFicha,
  calcularFichaDoItem,
  planoValido,
  variacoesDoItem,
  type FichaDoItem,
  type InsumoDaReceita,
  type ProcessoDaReceita,
  type VariacaoEscolhida,
} from "./ficha-de-producao";

/**
 * A ficha de cada item de um pedido, montada do lugar certo.
 *
 * Item vendido depois desta geração carrega a receita congelada na venda
 * (`production_snapshot`, gravada por gatilho): é ela que a produção segue,
 * mesmo que o cadastro do produto mude depois. Item antigo, ou de produto que
 * não tinha ficha na hora, lê o cadastro atual — o comportamento de sempre.
 *
 * Duas rotas usam isto (a ordem de produção e o painel de produção), e a
 * montagem vive num lugar só para o "previsto" ser o mesmo número nas duas.
 */

export type ItemDoPedidoParaFicha = {
  id: string;
  product_id: string | null;
  title: string;
  description: string | null;
  quantity: number | string | null;
  unit_type: string | null;
  width_m: number | string | null;
  height_m: number | string | null;
  length_m: number | string | null;
  selected_options?: unknown;
  production_snapshot?: unknown;
  production_minutes?: number | string | null;
  /* O plano de rodagem deste item (`order_items.production_plan`). */
  production_plan?: unknown;
  products?: { sku?: string | null; barcode?: string | null; production_templates?: unknown } | null;
};

export type Gabarito = { nome: string; url: string; formato: string };

export type FichaMontada = {
  itemId: string;
  ficha: FichaDoItem;
  variacoes: VariacaoEscolhida[];
  avisos: string[];
  gabaritos: Gabarito[];
  /* "venda": receita congelada no item; "cadastro": ficha atual do produto;
     "nenhuma": item sem produto ou produto sem ficha. */
  receitaDe: "venda" | "cadastro" | "nenhuma";
  receitaEm: string | null;
};

type MaterialResumo = {
  name?: string | null;
  unit?: string | null;
  purchase_format?: string | null;
  roll_width_m?: number | string | null;
  is_service?: boolean | null;
};

type ProductMaterialRow = {
  product_id: string;
  material_id?: string | null;
  consumption_qty: number | string | null;
  waste_pct: number | string | null;
  consumption_base?: string | null;
  pieces_per_unit?: number | string | null;
  materials?: MaterialResumo | null;
};

type ProductProcessRow = {
  product_id: string;
  kind?: string | null;
  process_name?: string | null;
  minutes?: number | string | null;
  setup_minutes?: number | string | null;
  sequence_order?: number | null;
  consumption_base?: string | null;
  created_at?: string | null;
  machines?: { name?: string | null } | null;
  labor_roles?: { role_name?: string | null } | null;
};

type OptionRecipeRow = {
  product_id: string;
  option_id: string;
  material_id?: string | null;
  qty_per_piece: number | string | null;
  materials?: MaterialResumo | null;
};

const BASES: BaseDeConsumo[] = ["por_peca", "por_m2", "por_m_largura", "por_m_altura", "por_perimetro"];

function baseValida(value: unknown): BaseDeConsumo {
  return BASES.includes(value as BaseDeConsumo) ? (value as BaseDeConsumo) : "por_peca";
}

function addToMap<T>(map: Map<string, T[]>, key: string, value: T) {
  const list = map.get(key);
  if (list) list.push(value);
  else map.set(key, [value]);
}

/* Os gabaritos do produto: `[{ format, url, name }]`. Entrada sem URL não é
   gabarito, e um JSON antigo em outro formato não pode derrubar a ficha. */
export function gabaritosDoProduto(raw: unknown): Gabarito[] {
  if (!Array.isArray(raw)) return [];
  const saida: Gabarito[] = [];
  for (const bruto of raw) {
    if (!bruto || typeof bruto !== "object" || Array.isArray(bruto)) continue;
    const linha = bruto as Record<string, unknown>;
    const url = typeof linha.url === "string" ? linha.url.trim() : "";
    if (!url) continue;
    const formato = typeof linha.format === "string" && linha.format.trim() ? linha.format.trim() : "pdf";
    const nome =
      typeof linha.name === "string" && linha.name.trim() ? linha.name.trim() : `Gabarito ${formato.toUpperCase()}`;
    saida.push({ nome, url, formato });
  }
  return saida;
}

/* Ordem de execução: a sequência gravada, e na falta dela a ordem em que a
   ficha foi montada — a receita da biblioteca já vem na ordem certa. */
function ordemDeExecucao(a: ProductProcessRow, b: ProductProcessRow) {
  const aOrder = typeof a.sequence_order === "number" ? a.sequence_order : 0;
  const bOrder = typeof b.sequence_order === "number" ? b.sequence_order : 0;
  if (aOrder !== bOrder) return aOrder - bOrder;
  const aAt = a.created_at || "";
  const bAt = b.created_at || "";
  if (aAt !== bAt) return aAt.localeCompare(bAt);
  return (a.process_name || "").localeCompare(b.process_name || "", "pt-BR");
}

async function getProcessRows(admin: SupabaseClient, productIds: string[], tenantIds: string[]) {
  const colunas = "product_id, kind, process_name, minutes, setup_minutes, sequence_order, consumption_base, created_at";
  const explicit = await admin
    .from("product_processes")
    .select(`${colunas}, machines:machines!machine_id(name), labor_roles:labor_roles!role_id(role_name)`)
    .in("product_id", productIds)
    .in("user_id", tenantIds);
  if (!explicit.error) return { data: (explicit.data || []) as unknown as ProductProcessRow[], error: null as string | null };

  const fallback = await admin
    .from("product_processes")
    .select(`${colunas}, machines(name), labor_roles(role_name)`)
    .in("product_id", productIds)
    .in("user_id", tenantIds);
  if (fallback.error) {
    return { data: [] as ProductProcessRow[], error: fallback.error.message || explicit.error.message || "Falha ao buscar processos." };
  }
  return { data: (fallback.data || []) as unknown as ProductProcessRow[], error: null as string | null };
}

/* A receita congelada, lida de volta no formato que a conta entende. */
function receitaDoSnapshot(raw: unknown): { insumos: InsumoDaReceita[]; processos: ProcessoDaReceita[]; capturadoEm: string | null } | null {
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
  const snap = raw as Record<string, unknown>;
  const insumosRaw = Array.isArray(snap.insumos) ? snap.insumos : [];
  const processosRaw = Array.isArray(snap.processos) ? snap.processos : [];
  if (insumosRaw.length === 0 && processosRaw.length === 0) return null;

  const insumos: InsumoDaReceita[] = [];
  for (const bruto of insumosRaw) {
    if (!bruto || typeof bruto !== "object") continue;
    const l = bruto as Record<string, unknown>;
    const opcao = typeof l.opcao === "string" ? l.opcao : "";
    insumos.push({
      nome: typeof l.nome === "string" && l.nome ? l.nome : "Material",
      unidade: typeof l.unidade === "string" && l.unidade ? l.unidade : "un",
      consumo: Number(l.consumo) || 0,
      base: baseValida(l.base),
      perdaPct: Number(l.perda_pct) || 0,
      origem: l.origem === "opcao" ? { tipo: "opcao", opcao: opcao || "opção" } : { tipo: "ficha" },
      materialId: typeof l.material_id === "string" && l.material_id ? l.material_id : null,
      /* Campos que o congelamento passou a gravar em 02/09/2026; snapshot
         antigo não tem, e `completarComOCadastro` preenche do cadastro. */
      rendimento: Number(l.rendimento) > 0 ? Number(l.rendimento) : null,
      formato: typeof l.formato === "string" && l.formato ? l.formato : undefined,
      larguraBobinaM: Number(l.largura_bobina) > 0 ? Number(l.largura_bobina) : null,
      servico: l.servico === true,
    });
  }

  const processos: ProcessoDaReceita[] = [];
  for (const bruto of processosRaw) {
    if (!bruto || typeof bruto !== "object") continue;
    const l = bruto as Record<string, unknown>;
    processos.push({
      nome: typeof l.nome === "string" && l.nome ? l.nome : "Processo",
      tipo: l.tipo === "maquina" ? "maquina" : l.tipo === "pessoa" ? "pessoa" : null,
      recurso: typeof l.recurso === "string" && l.recurso ? l.recurso : null,
      minutos: Number(l.minutos) || 0,
      base: baseValida(l.base),
      minutosDeSetup: Number(l.setup) || 0,
    });
  }

  return { insumos, processos, capturadoEm: typeof snap.capturado_em === "string" ? snap.capturado_em : null };
}

/* O cadastro atual dos materiais citados em snapshots antigos, e o rendimento
   gravado na ficha do produto para eles. */
async function completarComOCadastro(
  admin: SupabaseClient,
  items: ItemDoPedidoParaFicha[],
  tenantIds: string[]
): Promise<{ materiais: Map<string, MaterialResumo>; rendimentos: Map<string, number> }> {
  const materiais = new Map<string, MaterialResumo>();
  const rendimentos = new Map<string, number>();
  const ids = new Set<string>();
  const productIds = new Set<string>();
  for (const item of items) {
    const snap = receitaDoSnapshot(item.production_snapshot);
    if (!snap) continue;
    for (const i of snap.insumos) {
      if (i.formato === undefined && i.materialId) {
        ids.add(i.materialId);
        if (item.product_id) productIds.add(item.product_id);
      }
    }
  }
  if (ids.size === 0) return { materiais, rendimentos };

  type LinhaDeRendimento = { product_id: string; material_id: string | null; pieces_per_unit: number | string | null };
  const [mats, pms] = await Promise.all([
    admin.from("materials").select("id, purchase_format, roll_width_m, is_service").in("id", [...ids]).in("user_id", tenantIds),
    productIds.size > 0
      ? admin.from("product_materials").select("product_id, material_id, pieces_per_unit").in("product_id", [...productIds]).in("material_id", [...ids])
      : Promise.resolve({ data: [] as LinhaDeRendimento[], error: null }),
  ]);
  for (const m of (mats.data || []) as (MaterialResumo & { id: string })[]) materiais.set(m.id, m);
  for (const pm of (pms.data || []) as LinhaDeRendimento[]) {
    if (pm.material_id && Number(pm.pieces_per_unit) > 0) rendimentos.set(`${pm.product_id}|${pm.material_id}`, Number(pm.pieces_per_unit));
  }
  return { materiais, rendimentos };
}

export async function montarFichasDoPedido(
  admin: SupabaseClient,
  items: ItemDoPedidoParaFicha[],
  tenantIds: string[]
): Promise<{ fichas: FichaMontada[]; error: string | null }> {
  /* Só os itens sem receita congelada precisam do cadastro. */
  const semSnapshot = items.filter((item) => item.product_id && !receitaDoSnapshot(item.production_snapshot));
  const productIds = Array.from(new Set(semSnapshot.map((i) => i.product_id as string)));

  const insumosPorProduto = new Map<string, InsumoDaReceita[]>();
  const processosPorProduto = new Map<string, ProcessoDaReceita[]>();
  const receitaPorOpcao = new Map<string, { nome: string; unidade: string; consumo: number; materialId: string | null; formato: string | null; servico: boolean }[]>();

  if (productIds.length > 0) {
    const [materialsResult, processResult, recipesResult] = await Promise.all([
      admin
        .from("product_materials")
        .select("product_id, material_id, consumption_qty, waste_pct, consumption_base, pieces_per_unit, materials:materials!material_id(name, unit, purchase_format, roll_width_m, is_service)")
        .in("product_id", productIds)
        .in("user_id", tenantIds)
        .order("ordem", { nullsFirst: false })
        .order("id"),
      getProcessRows(admin, productIds, tenantIds),
      admin
        .from("product_option_recipes")
        .select("product_id, option_id, material_id, qty_per_piece, materials:materials!material_id(name, unit, purchase_format, roll_width_m, is_service)")
        .in("product_id", productIds)
        .in("user_id", tenantIds)
        .eq("active", true),
    ]);

    if (materialsResult.error) return { fichas: [], error: materialsResult.error.message };
    if (processResult.error) return { fichas: [], error: processResult.error };
    if (recipesResult.error) return { fichas: [], error: recipesResult.error.message };

    /* `product_materials` não guarda a ordem em que a ficha foi montada;
       por nome é a ordem que a bancada consegue procurar. */
    ((materialsResult.data || []) as unknown as ProductMaterialRow[])
      .sort((a, b) => (a.materials?.name || "").localeCompare(b.materials?.name || "", "pt-BR"))
      .forEach((row) => {
        addToMap(insumosPorProduto, row.product_id, {
          nome: row.materials?.name || "Material",
          unidade: row.materials?.unit || "un",
          consumo: Number(row.consumption_qty) || 0,
          base: baseValida(row.consumption_base),
          perdaPct: Number(row.waste_pct) || 0,
          origem: { tipo: "ficha" },
          materialId: row.material_id ?? null,
          rendimento: Number(row.pieces_per_unit) > 0 ? Number(row.pieces_per_unit) : null,
          formato: row.materials?.purchase_format ?? null,
          larguraBobinaM: Number(row.materials?.roll_width_m) > 0 ? Number(row.materials?.roll_width_m) : null,
          servico: row.materials?.is_service === true,
        });
      });

    processResult.data.sort(ordemDeExecucao).forEach((row) => {
      const recurso = row.machines?.name || row.labor_roles?.role_name || null;
      addToMap(processosPorProduto, row.product_id, {
        nome: row.process_name || recurso || "Processo",
        tipo: row.kind === "MACHINE" ? "maquina" : row.kind === "ROLE" ? "pessoa" : null,
        recurso,
        minutos: Number(row.minutes) || 0,
        base: baseValida(row.consumption_base),
        minutosDeSetup: Number(row.setup_minutes) || 0,
      });
    });

    ((recipesResult.data || []) as unknown as OptionRecipeRow[]).forEach((row) => {
      addToMap(receitaPorOpcao, row.option_id, {
        nome: row.materials?.name || "Material",
        unidade: row.materials?.unit || "un",
        consumo: Number(row.qty_per_piece) || 0,
        materialId: row.material_id ?? null,
        formato: row.materials?.purchase_format ?? null,
        servico: row.materials?.is_service === true,
      });
    });
  }

  /* Snapshot de antes de 02/09/2026 não sabe formato, bobina, serviço nem
     rendimento: completa do cadastro atual, sem mexer no que foi congelado. */
  const complemento = await completarComOCadastro(admin, items, tenantIds);

  const fichas = items.map((item): FichaMontada => {
    const variacoes = variacoesDoItem(item.selected_options);
    const snapshot = receitaDoSnapshot(item.production_snapshot);

    let insumos: InsumoDaReceita[] = [];
    let processos: ProcessoDaReceita[] = [];
    let receitaDe: FichaMontada["receitaDe"] = "nenhuma";
    let receitaEm: string | null = null;

    if (snapshot) {
      insumos = snapshot.insumos.map((i) => {
        if (i.formato !== undefined || !i.materialId) return i;
        const m = complemento.materiais.get(i.materialId);
        const rendimento = item.product_id ? complemento.rendimentos.get(`${item.product_id}|${i.materialId}`) ?? null : null;
        return {
          ...i,
          formato: m?.purchase_format ?? null,
          larguraBobinaM: Number(m?.roll_width_m) > 0 ? Number(m?.roll_width_m) : null,
          servico: m?.is_service === true,
          rendimento: i.rendimento ?? rendimento,
        };
      });
      processos = snapshot.processos;
      receitaDe = "venda";
      receitaEm = snapshot.capturadoEm;
    } else if (item.product_id) {
      const daFicha = insumosPorProduto.get(item.product_id) || [];
      /* A receita da opção é por peça, sempre: o pingente vai em cada
         chaveiro, não em cada m². */
      const dasOpcoes: InsumoDaReceita[] = variacoes.flatMap((v) =>
        (v.optionId ? receitaPorOpcao.get(v.optionId) || [] : []).map((r) => ({
          nome: r.nome,
          unidade: r.unidade,
          consumo: r.consumo,
          base: "por_peca" as const,
          perdaPct: 0,
          origem: { tipo: "opcao" as const, opcao: v.valor },
          materialId: r.materialId,
          formato: r.formato,
          servico: r.servico,
        }))
      );
      insumos = [...daFicha, ...dasOpcoes];
      processos = processosPorProduto.get(item.product_id) || [];
      if (insumos.length > 0 || processos.length > 0) receitaDe = "cadastro";
    }

    const ficha = calcularFichaDoItem({
      quantidade: Number(item.quantity) || 0,
      larguraM: Number(item.width_m) || 0,
      alturaM: Number(item.height_m) || 0,
      insumos,
      processos,
      plano: planoValido(item.production_plan),
    });

    return {
      itemId: item.id,
      ficha,
      variacoes,
      avisos: avisosDaFicha(ficha, { unitType: item.unit_type }),
      gabaritos: gabaritosDoProduto(item.products?.production_templates),
      receitaDe,
      receitaEm,
    };
  });

  return { fichas, error: null };
}
