import { randomUUID } from "node:crypto";

import sharp from "sharp";

import { normalizeProductCustomFieldsConfig } from "@/lib/product-custom-fields";
import { alterarProdutoComoOPainel, criarProdutoComoOPainel } from "@/lib/produtos/servidor/gravar-produto";
import { lerGrupos, lerServicos, type GrupoDeServicos, type ServicoOpcional } from "@/lib/servicos-opcionais";
import { lerVinculosDeServico } from "@/lib/vinculos-de-servico";

import type { ContextoDoAssistente } from "../contexto";
import { receberImagem } from "../imagens";
import { falha, recusar } from "./comum";
import {
  atributosDaFicha,
  comoGuardarAFoto,
  EXTENSAO_DO_FORMATO,
  fotosDaFicha,
  LADO_MAIOR,
  type AtributoDaFicha,
  type CampoPersonalizado,
  type FotoDaFicha,
  type VariacaoDaFicha,
  type Vinculo,
} from "./produto-completo-regras";
import { ErroDaFerramenta } from "./tipos";

/*
 * A ficha do produto como a tela do painel lê, e a gravação pelo mesmo
 * caminho da tela (lib/produtos/servidor/gravar-produto.ts).
 */

export type FaixaDaFicha = { id?: string; min_qty: number; max_qty: number | null; unit_price: number; sort_order: number; active: boolean };

export type Ficha = {
  produto: Record<string, unknown> & { id: string; user_id: string; name: string };
  atributos: AtributoDaFicha[];
  variacoes: VariacaoDaFicha[];
  fotos: FotoDaFicha[];
  faixas: FaixaDaFicha[];
  vinculos: Vinculo[];
  campos: CampoPersonalizado[];
};

const SELECAO =
  "*, categories(id,name), product_processes(id,cost_total,minutes,setup_minutes,consumption_base,kind,process_name,machine_id,role_id), product_materials(id,material_id,consumption_qty,waste_pct,cost_total,pieces_per_unit,ordem), product_attributes(*), product_price_tiers(id,min_qty,max_qty,unit_price,sort_order,active), product_images(id,image_url,sort_order,is_primary)";

/** O produto inteiro, do jeito que a tela do produto abre (fetchProduct). */
export async function lerFicha(ctx: ContextoDoAssistente, produtoId: string): Promise<Ficha> {
  const { data, error } = await ctx.supabase.from("products").select(SELECAO).eq("id", produtoId).in("user_id", ctx.tenantIds).maybeSingle();
  if (error) falha("ler o produto", error);
  if (!data) recusar("Produto não encontrado. Use buscar_produtos para achar o id certo.");
  const produto = data as Ficha["produto"];
  /* A composição na ordem da tela (coluna ordem, migration 214; empate pelo id): o embed vinha cada vez numa ordem. */
  if (Array.isArray(produto.product_materials)) {
    (produto.product_materials as Array<{ id?: string; ordem?: number | null }>).sort(
      (a, b) => (a.ordem ?? Number.MAX_SAFE_INTEGER) - (b.ordem ?? Number.MAX_SAFE_INTEGER) || String(a.id ?? "").localeCompare(String(b.id ?? ""))
    );
  }
  if (produto.product_kind === "digital") recusar("Este é um produto digital: use as ferramentas de produtos digitais (ver_produto_digital, alterar_produto_digital).");

  const [estoques, receitas, variacoes, vinculos] = await Promise.all([
    ctx.supabase.from("product_option_stocks").select("option_id,stock,minimum_stock,reorder_point").eq("product_id", produtoId).eq("active", true),
    ctx.supabase.from("product_option_recipes").select("option_id,material_id,qty_per_piece").eq("product_id", produtoId).eq("active", true),
    ctx.supabase.from("product_variations").select("id,combination,sku,price,stock,cost_adjustment,active").eq("product_id", produtoId).eq("active", true),
    ctx.supabase.from("product_optional_services").select("service_id,enabled").eq("product_id", produtoId),
  ]);

  const atributos = atributosDaFicha(
    produto.product_attributes as Parameters<typeof atributosDaFicha>[0],
    (estoques.data ?? []) as Parameters<typeof atributosDaFicha>[1],
    (receitas.data ?? []) as Parameters<typeof atributosDaFicha>[2]
  );
  const fotos = fotosDaFicha(produto.product_images as Array<Record<string, unknown>> | null, (produto.image_url as string | null) ?? null, randomUUID);
  const faixas = ((produto.product_price_tiers ?? []) as Array<Record<string, unknown>>)
    .map((faixa, indice) => ({
      id: (faixa.id as string | undefined) ?? undefined,
      min_qty: Number(faixa.min_qty) || 0,
      max_qty: faixa.max_qty === null || faixa.max_qty === undefined ? null : Number(faixa.max_qty) || 0,
      unit_price: Number(faixa.unit_price) || 0,
      sort_order: typeof faixa.sort_order === "number" ? faixa.sort_order : indice,
      active: (faixa.active as boolean | null) ?? true,
    }))
    .sort((a, b) => a.sort_order - b.sort_order);

  return {
    produto,
    atributos,
    variacoes: ((variacoes.data ?? []) as Array<Record<string, unknown>>).map((v) => ({
      id: (v.id as string | undefined) ?? undefined,
      combination: (v.combination as Record<string, string> | null) ?? {},
      sku: (v.sku as string | null) || "",
      price: (v.price as number | null) ?? null,
      stock: (v.stock as number | null) ?? 0,
      cost_adjustment: (v.cost_adjustment as number | null) ?? 0,
    })),
    fotos,
    faixas,
    vinculos: lerVinculosDeServico(vinculos.data),
    campos: normalizeProductCustomFieldsConfig(produto.custom_fields_config) as CampoPersonalizado[],
  };
}

/** O escopo do estoque que a aba Variações recebe: sem controle de estoque, "por produto". */
export function escopoEfetivo(produto: Record<string, unknown>) {
  return produto.track_inventory ? String(produto.inventory_scope || "product") : "product";
}

/* ================================================================ gravar */

function pedidoDoAssistente(ctx: ContextoDoAssistente) {
  return new Request(`${ctx.origemDoAdmin}/api/mcp`);
}

async function lerResposta(resposta: Response, oQue: string) {
  const corpo = (await resposta.json().catch(() => null)) as { error?: string; id?: string | null } | null;
  if (resposta.ok) return corpo ?? {};
  if (resposta.status === 403) recusar(`Esta pessoa não tem permissão para ${oQue}. Quem libera é o dono da empresa, no painel, em Funcionários.`);
  if (resposta.status === 404) recusar("Produto não encontrado. Nada foi gravado.");
  if (resposta.status < 500 && corpo?.error) recusar(`Não deu para ${oQue}: ${corpo.error} Nada foi gravado.`);
  falha(oQue, { message: corpo?.error ?? `status ${resposta.status}` });
}

/**
 * Grava um pedaço da ficha pelo Salvar da tela do produto (PATCH de
 * /api/products, mesma função). `patch` tem os mesmos campos que a tela manda.
 */
export async function gravarComoOPainel(ctx: ContextoDoAssistente, produtoId: string, patch: Record<string, unknown>, oQue = "alterar o produto") {
  const resposta = await alterarProdutoComoOPainel({
    supabase: ctx.supabase,
    user: { id: ctx.user.id },
    obterContexto: async () => ctx.empresa,
    body: { id: produtoId, patch },
    req: pedidoDoAssistente(ctx),
  });
  await lerResposta(resposta, oQue);
}

/** Cadastra pelo mesmo POST de /api/products. Devolve o id novo. */
export async function cadastrarComoOPainel(ctx: ContextoDoAssistente, corpo: Record<string, unknown>): Promise<string> {
  const resposta = await criarProdutoComoOPainel({
    supabase: ctx.supabase,
    user: { id: ctx.user.id },
    obterContexto: async () => ctx.empresa,
    body: corpo,
    req: pedidoDoAssistente(ctx),
  });
  const lido = await lerResposta(resposta, "cadastrar o produto");
  const id = (lido as { id?: string | null }).id;
  if (!id) falha("cadastrar o produto", { message: "sem id" });
  return id;
}

/* ================================================================= fotos */

export type FotoPronta = { conteudo: Buffer; tipo: string; extensao: string; largura: number; altura: number; origem: string; encolhida: boolean };

/**
 * A imagem (envio, link ou produto:<id>) pronta para a galeria, como o
 * navegador prepara antes de subir (lib/fotos/encolher.ts).
 */
export async function fotoParaOProduto(ctx: ContextoDoAssistente, valor: unknown): Promise<FotoPronta> {
  const recebida = await receberImagem(ctx, valor, { minimo: 48 });
  const original = EXTENSAO_DO_FORMATO[recebida.formato] ?? { extensao: "png", tipo: "image/png" };
  const decisao = comoGuardarAFoto({ formato: recebida.formato, largura: recebida.largura, altura: recebida.altura, bytes: recebida.conteudo.length });
  if (decisao === "original") {
    return { conteudo: recebida.conteudo, ...original, largura: recebida.largura, altura: recebida.altura, origem: recebida.origem, encolhida: false };
  }
  try {
    /* rotate(): a orientação dos metadados, como o "from-image" do navegador. */
    const { data, info } = await sharp(recebida.conteudo, { limitInputPixels: 60_000_000, animated: false })
      .rotate()
      .resize(LADO_MAIOR, LADO_MAIOR, { fit: "inside", withoutEnlargement: true })
      .webp({ quality: 85 })
      .toBuffer({ resolveWithObject: true });
    const maior = Math.max(recebida.largura, recebida.altura);
    /* Saiu maior e nem precisava encolher: fica a original (a mesma regra do navegador). */
    if (data.length >= recebida.conteudo.length && maior <= LADO_MAIOR) {
      return { conteudo: recebida.conteudo, ...original, largura: recebida.largura, altura: recebida.altura, origem: recebida.origem, encolhida: false };
    }
    return { conteudo: data, tipo: "image/webp", extensao: "webp", largura: info.width, altura: info.height, origem: recebida.origem, encolhida: true };
  } catch {
    /* Encolher é melhoria, nunca motivo para falhar: sobe a original. */
    return { conteudo: recebida.conteudo, ...original, largura: recebida.largura, altura: recebida.altura, origem: recebida.origem, encolhida: false };
  }
}

/** Sobe no depósito `products`, pasta `products/`, com o nome que a galeria usa. */
export async function guardarFotoDoProduto(ctx: ContextoDoAssistente, foto: FotoPronta): Promise<{ url: string; caminho: string }> {
  const caminho = `products/${randomUUID()}_${Date.now()}.${foto.extensao}`;
  const { error } = await ctx.supabase.storage.from("products").upload(caminho, foto.conteudo, { contentType: foto.tipo, upsert: false });
  if (error) {
    console.error("[mcp] subir foto do produto:", error.message);
    throw new ErroDaFerramenta("Não deu para guardar a foto agora. Tente de novo em instantes.");
  }
  return { url: ctx.supabase.storage.from("products").getPublicUrl(caminho).data.publicUrl, caminho };
}

export async function desfazerFotos(ctx: ContextoDoAssistente, caminhos: string[]) {
  if (caminhos.length) await ctx.supabase.storage.from("products").remove(caminhos).catch(() => undefined);
}

/* ========================================================== biblioteca */

export type Biblioteca = { grupos: GrupoDeServicos[]; servicos: ServicoOpcional[] };

/** A biblioteca de serviços opcionais da empresa (a mesma que a tela do produto lê). */
export async function bibliotecaDeServicos(ctx: ContextoDoAssistente): Promise<Biblioteca> {
  const [grupos, servicos] = await Promise.all([
    ctx.supabase.from("service_groups").select("*").eq("user_id", ctx.donoDosDados).order("position"),
    ctx.supabase.from("optional_services").select("*").eq("user_id", ctx.donoDosDados).order("position"),
  ]);
  return { grupos: lerGrupos(grupos.data ?? []), servicos: lerServicos(servicos.data ?? []) };
}

/** Os insumos, máquinas e mãos de obra da empresa (o que a ficha técnica e a receita da opção usam). */
export async function cadastrosDeProducao(ctx: ContextoDoAssistente) {
  const empresa = ctx.empresa.companyId;
  const [insumos, maquinas, funcoes] = await Promise.all([
    ctx.admin.from("materials").select("id,name,unit,cost_per_unit,waste_pct_default").eq("user_id", empresa).order("name").limit(2000),
    ctx.admin.from("machines").select("id,name,hourly_cost").eq("user_id", empresa).order("name").limit(500),
    ctx.admin.from("labor_roles").select("id,role_name,hourly_rate").eq("user_id", empresa).order("role_name").limit(500),
  ]);
  return {
    insumos: (insumos.data ?? []) as Array<{ id: string; name: string; unit: string | null; cost_per_unit: number | string | null; waste_pct_default: number | string | null }>,
    maquinas: (maquinas.data ?? []) as Array<{ id: string; name: string; hourly_cost: number | string | null }>,
    funcoes: (funcoes.data ?? []) as Array<{ id: string; role_name: string; hourly_rate: number | string | null }>,
  };
}

/** Quantos orçamentos e pedidos usam o produto, e quais opções (pelo id) aparecem neles. */
export async function usoDoProduto(ctx: ContextoDoAssistente, produtoId: string) {
  const [orcamentos, pedidos] = await Promise.all([
    ctx.supabase.from("quote_items").select("selected_options").eq("product_id", produtoId).limit(2000),
    ctx.supabase.from("order_items").select("selected_options").eq("product_id", produtoId).limit(2000),
  ]);
  const textos = [...(orcamentos.data ?? []), ...(pedidos.data ?? [])].map((linha) => JSON.stringify((linha as { selected_options?: unknown }).selected_options ?? ""));
  return {
    orcamentos: (orcamentos.data ?? []).length,
    pedidos: (pedidos.data ?? []).length,
    usaAOpcao: (id: string | null, rotulo: string) => textos.filter((texto) => (id && texto.includes(id)) || texto.includes(JSON.stringify(rotulo))).length,
  };
}
