/**
 * A importação no banco: o que já foi trazido, o limite do dia, as
 * categorias e a gravação do lote.
 *
 * Produto, variação, faixa de preço e categoria são gravados com a sessão da
 * própria pessoa (as travas do banco valem, como no painel). O registro da
 * importação e a fila das fotos são das tabelas dos assistentes, que só o
 * servidor lê e grava, sempre com a empresa conferida.
 */

import { randomUUID } from "node:crypto";
import type { SupabaseClient } from "@supabase/supabase-js";

import { avisarLoja } from "@/lib/loja/avisar-loja";
import { custoDoProduto } from "@/lib/precificacao/custo-do-produto";

import type { ContextoDoAssistente } from "../contexto";
import { acordarFilaDeFotos } from "./fila-de-fotos";
import type { ProdutoMontado } from "./montar";

/* Freios contra uso que não é migração: uma loja de 3 mil produtos leva três dias. */
export const LIMITE_DIARIO_DE_PRODUTOS = 1000;
export const LIMITE_DIARIO_DE_FOTOS = 8000;

const UM_DIA_MS = 24 * 60 * 60 * 1000;

export async function usoDeHoje(admin: SupabaseClient, companyId: string): Promise<{ produtos: number; fotos: number }> {
  const desde = new Date(Date.now() - UM_DIA_MS).toISOString();
  const [itens, fotos] = await Promise.all([
    admin.from("importacao_itens").select("id", { count: "exact", head: true }).eq("company_id", companyId).gte("criado_em", desde),
    admin.from("importacao_fotos").select("id", { count: "exact", head: true }).eq("company_id", companyId).gte("criada_em", desde),
  ]);
  if (itens.error) throw itens.error;
  if (fotos.error) throw fotos.error;
  return { produtos: itens.count ?? 0, fotos: fotos.count ?? 0 };
}

export type JaTrazido = { produtoId: string; nome: string | null; criadoEm: string };

/** Os produtos que já vieram destes endereços, nesta empresa. */
export async function jaTrazidos(admin: SupabaseClient, companyId: string, chaves: string[]): Promise<Map<string, JaTrazido>> {
  const mapa = new Map<string, JaTrazido>();
  const unicas = [...new Set(chaves.filter(Boolean))];
  for (let inicio = 0; inicio < unicas.length; inicio += 100) {
    const { data, error } = await admin
      .from("importacao_itens")
      .select("origem_chave,product_id,nome,criado_em")
      .eq("company_id", companyId)
      .in("origem_chave", unicas.slice(inicio, inicio + 100));
    if (error) throw error;
    for (const linha of (data ?? []) as Array<{ origem_chave: string; product_id: string; nome: string | null; criado_em: string }>) {
      mapa.set(linha.origem_chave, { produtoId: linha.product_id, nome: linha.nome, criadoEm: linha.criado_em });
    }
  }
  return mapa;
}

/* -------------------------------------------------------------- categorias */

export type Categoria = { id: string; name: string; slug: string; parent_id: string | null };

const normal = (texto: string) =>
  texto
    .normalize("NFD")
    .replace(/[̀-ͯ]/g, "")
    .toLowerCase()
    .replace(/\s+/g, " ")
    .trim();

export function apelido(texto: string) {
  return texto
    .toLowerCase()
    .normalize("NFD")
    .replace(/[̀-ͯ]/g, "")
    .replace(/[^\w\s-]/g, "")
    .trim()
    .replace(/[\s_]+/g, "-")
    .replace(/-+/g, "-")
    .replace(/^-|-$/g, "");
}

export const chaveDoCaminho = (caminho: string[]) => caminho.map(normal).join(" > ");

/**
 * Para cada caminho ("Camisetas > Masculinas"), a categoria que já existe ou
 * a que vai nascer. Nome igual (sem diferença de acento e maiúscula) é a
 * mesma categoria; a de dentro só é a mesma se estiver debaixo da mesma de
 * fora.
 */
export function planejarCategorias(existentes: Categoria[], caminhos: string[][]) {
  const porNome = (nome: string, pai: string | null | undefined) =>
    existentes.find((categoria) => normal(categoria.name) === normal(nome) && (pai === undefined || (categoria.parent_id ?? null) === pai));
  /* `pai` é a chave da de fora; `paiId`, o id dela quando ela já existe. */
  const novas = new Map<string, { nome: string; pai: string | null; paiId: string | null }>();
  const destino = new Map<string, { id: string | null; nova: boolean }>();

  for (const caminho of caminhos) {
    if (!caminho.length) continue;
    const chave = chaveDoCaminho(caminho);
    if (destino.has(chave)) continue;
    const [primeira, segunda] = caminho;
    const fora = porNome(primeira, null) ?? porNome(primeira, undefined);
    const chaveDeFora = chaveDoCaminho([primeira]);
    if (!fora) novas.set(chaveDeFora, { nome: primeira, pai: null, paiId: null });
    if (!segunda) {
      destino.set(chave, { id: fora?.id ?? null, nova: !fora });
      continue;
    }
    const dentro = fora ? porNome(segunda, fora.id) : undefined;
    if (!dentro) novas.set(chave, { nome: segunda, pai: chaveDeFora, paiId: fora?.id ?? null });
    destino.set(chave, { id: dentro?.id ?? null, nova: !dentro });
  }
  return { destino, novas };
}

export async function categoriasDaEmpresa(ctx: ContextoDoAssistente): Promise<Categoria[]> {
  const { data, error } = await ctx.supabase.from("categories").select("id,name,slug,parent_id").in("user_id", ctx.tenantIds);
  if (error) throw error;
  return (data ?? []) as Categoria[];
}

/** Cria as categorias que faltam (as de fora primeiro). Devolve o id de cada caminho. */
async function criarCategorias(ctx: ContextoDoAssistente, caminhos: string[][]): Promise<Map<string, string | null>> {
  const existentes = await categoriasDaEmpresa(ctx);
  const { destino, novas } = planejarCategorias(existentes, caminhos);
  const ids = new Map<string, string | null>();
  for (const [chave, item] of destino) if (item.id) ids.set(chave, item.id);

  const criadas = new Map<string, string>();
  const ordem = [...novas].sort(([, a], [, b]) => Number(a.pai !== null) - Number(b.pai !== null));
  for (const [chave, nova] of ordem) {
    const paiId = nova.pai ? (nova.paiId ?? criadas.get(nova.pai) ?? null) : null;
    if (nova.pai && !paiId) continue;
    const base = apelido(nova.pai ? `${nova.pai.split(" > ").pop()} ${nova.nome}` : nova.nome) || "categoria";
    let id: string | null = null;
    for (let tentativa = 0; tentativa < 5 && !id; tentativa += 1) {
      const slug = tentativa ? `${base}-${tentativa + 1}` : base;
      const { data, error } = await ctx.supabase
        .from("categories")
        .insert({ user_id: ctx.donoDosDados, name: nova.nome, slug, parent_id: paiId })
        .select("id")
        .single();
      if (!error && data) id = (data as { id: string }).id;
      else if (error?.code !== "23505") break;
    }
    if (id) {
      criadas.set(chave, id);
      if (!nova.pai) ids.set(chave, id);
    }
  }
  for (const [chave, item] of destino) if (!item.id) ids.set(chave, criadas.get(chave) ?? null);
  return ids;
}

/* -------------------------------------------------------------------- lote */

export type CargaDoLote = {
  dominio: string | null;
  plataforma: string | null;
  produtos: ProdutoMontado[];
  /* A pessoa pode criar categoria? Sem isso, produto de categoria nova fica sem categoria. */
  criaCategorias: boolean;
};

export type ResultadoDoLote = {
  loteId: string;
  criados: Array<{ id: string; nome: string }>;
  fotos: number;
  problemas: string[];
};

function linhaDoProduto(ctx: ContextoDoAssistente, produto: ProdutoMontado, id: string, categoriaId: string | null) {
  return {
    id,
    user_id: ctx.donoDosDados,
    name: produto.nome,
    description: produto.descricao,
    price: produto.preco,
    price_on_request: produto.sobConsulta,
    use_variation_price_only: false,
    unit_type: produto.unidade,
    category_id: categoriaId,
    estimated_base_cost: 0,
    cost_total: custoDoProduto({ estimatedBaseCost: 0, somaDaFicha: 0, unitType: produto.unidade }),
    sku: produto.codigo,
    barcode: produto.codigoDeBarras,
    image_url: null,
    show_in_catalog: false,
    active: true,
    is_catalog_exclusive: false,
    track_inventory: false,
    inventory_scope: "product",
    inventory_attribute_name: null,
    allow_negative_stock: false,
    min_sale_qty_enabled: produto.quantidadeMinima !== null,
    min_sale_qty: produto.quantidadeMinima,
    max_sale_qty_enabled: false,
    max_sale_qty: null,
    allowed_sale_qty_enabled: false,
    allowed_sale_quantities: [],
    production_time_value: produto.prazo?.valor ?? null,
    production_time_unit: produto.prazo?.unidade ?? null,
    shipping_weight_kg: produto.pesoKg,
  };
}

export async function gravarLote(ctx: ContextoDoAssistente, carga: CargaDoLote): Promise<ResultadoDoLote> {
  const problemas: string[] = [];
  const companyId = ctx.chave.companyId;

  const { data: lote, error: erroDoLote } = await ctx.admin
    .from("importacoes")
    .insert({ company_id: companyId, dono_id: ctx.donoDosDados, usuario_id: ctx.user.id, chave_id: ctx.chave.id, origem: carga.dominio, plataforma: carga.plataforma })
    .select("id")
    .single();
  if (erroDoLote || !lote) throw erroDoLote ?? new Error("lote sem id");
  const loteId = (lote as { id: string }).id;

  /* Categorias: as que existem, e as novas (quando a pessoa pode criar). */
  const caminhos = carga.produtos.map((produto) => produto.categorias).filter((caminho) => caminho.length);
  let categorias = new Map<string, string | null>();
  if (caminhos.length) {
    if (carga.criaCategorias) categorias = await criarCategorias(ctx, caminhos);
    else {
      const { destino } = planejarCategorias(await categoriasDaEmpresa(ctx), caminhos);
      for (const [chave, item] of destino) categorias.set(chave, item.id);
    }
  }

  /* Produtos: de uma vez; se o banco recusar o lote, um a um, para saber qual. */
  const linhas = carga.produtos.map((produto) => ({ produto, id: randomUUID() }));
  const paraGravar = linhas.map(({ produto, id }) => linhaDoProduto(ctx, produto, id, produto.categorias.length ? (categorias.get(chaveDoCaminho(produto.categorias)) ?? null) : null));
  const gravados = new Set<string>();
  const { error: erroDosProdutos } = await ctx.supabase.from("products").insert(paraGravar);
  if (!erroDosProdutos) linhas.forEach(({ id }) => gravados.add(id));
  else {
    for (const [indice, linha] of paraGravar.entries()) {
      const { error } = await ctx.supabase.from("products").insert(linha);
      if (error) problemas.push(`${linhas[indice].produto.nome}: não foi cadastrado (${error.message.slice(0, 120)}).`);
      else gravados.add(linha.id);
    }
  }
  const criados = linhas.filter(({ id }) => gravados.has(id));
  if (!criados.length) {
    await ctx.admin.from("importacoes").delete().eq("id", loteId);
    throw new Error(problemas[0] ?? "nenhum produto foi cadastrado");
  }

  /* Variações e faixas de preço. */
  const atributos = criados.flatMap(({ produto, id }) =>
    produto.atributos.map((atributo) => ({ product_id: id, name: atributo.name, options: atributo.options, position: atributo.position, allow_multiple: false, is_required: true, show_in_store: true }))
  );
  if (atributos.length) {
    const { error } = await ctx.supabase.from("product_attributes").insert(atributos);
    if (error) problemas.push(`As variações não foram gravadas (${error.message.slice(0, 120)}). Os produtos ficaram sem elas.`);
  }
  const faixas = criados.flatMap(({ produto, id }) =>
    produto.faixas.map((faixa, indice) => ({ product_id: id, user_id: ctx.donoDosDados, min_qty: faixa.min_qty, max_qty: faixa.max_qty, unit_price: faixa.unit_price, sort_order: indice, active: true }))
  );
  if (faixas.length) {
    const { error } = await ctx.supabase.from("product_price_tiers").insert(faixas);
    if (error) problemas.push(`As faixas de preço não foram gravadas (${error.message.slice(0, 120)}).`);
  }

  /* O registro de cada produto (é o que impede trazer de novo) e a fila das fotos. */
  const itens = criados.map(({ produto, id }) => ({ importacao_id: loteId, company_id: companyId, product_id: id, origem_chave: produto.chave, nome: produto.nome }));
  const { error: erroDosItens } = await ctx.admin.from("importacao_itens").insert(itens);
  if (erroDosItens) {
    for (const item of itens) {
      const { error } = await ctx.admin.from("importacao_itens").insert(item);
      if (error && error.code !== "23505") console.error("[importacao] item sem registro:", item.product_id, error.message);
    }
  }
  const fotos = criados.flatMap(({ produto, id }) =>
    produto.fotos.map((foto) => ({ id: foto.id, importacao_id: loteId, company_id: companyId, dono_id: ctx.donoDosDados, product_id: id, endereco: foto.endereco, posicao: foto.posicao }))
  );
  let naFila = 0;
  for (let inicio = 0; inicio < fotos.length; inicio += 200) {
    const pedaco = fotos.slice(inicio, inicio + 200);
    const { error } = await ctx.admin.from("importacao_fotos").insert(pedaco);
    if (error) problemas.push(`Parte das fotos não entrou na fila (${error.message.slice(0, 120)}).`);
    else naFila += pedaco.length;
  }
  await ctx.admin.from("importacoes").update({ produtos: criados.length, fotos: naFila }).eq("id", loteId);

  avisarLoja(ctx.donoDosDados, new Request(`${ctx.origemDoAdmin}/api/mcp`));
  if (naFila) acordarFilaDeFotos();

  return { loteId, criados: criados.map(({ produto, id }) => ({ id, nome: produto.nome })), fotos: naFila, problemas };
}

/* ------------------------------------------------------------ acompanhar */

export type SituacaoDoLote = {
  id: string;
  criadaEm: string;
  origem: string | null;
  plataforma: string | null;
  produtos: number;
  fotos: { prontas: number; naFila: number; falharam: number };
  falhas: Array<{ endereco: string; erro: string | null }>;
};

/** Os lotes da empresa (o pedido, ou os últimos), com a contagem das fotos. */
export async function situacaoDosLotes(admin: SupabaseClient, companyId: string, loteId?: string, quantos = 10): Promise<SituacaoDoLote[]> {
  let consulta = admin.from("importacoes").select("id,criada_em,origem,plataforma,produtos").eq("company_id", companyId).order("criada_em", { ascending: false }).limit(quantos);
  if (loteId) consulta = consulta.eq("id", loteId);
  const { data, error } = await consulta;
  if (error) throw error;
  const lotes = (data ?? []) as Array<{ id: string; criada_em: string; origem: string | null; plataforma: string | null; produtos: number }>;
  if (!lotes.length) return [];

  const { data: fotos, error: erroDasFotos } = await admin
    .from("importacao_fotos")
    .select("importacao_id,situacao,endereco,erro")
    .in(
      "importacao_id",
      lotes.map((lote) => lote.id)
    )
    .limit(20_000);
  if (erroDasFotos) throw erroDasFotos;

  return lotes.map((lote) => {
    const doLote = ((fotos ?? []) as Array<{ importacao_id: string; situacao: string; endereco: string; erro: string | null }>).filter((foto) => foto.importacao_id === lote.id);
    return {
      id: lote.id,
      criadaEm: lote.criada_em,
      origem: lote.origem,
      plataforma: lote.plataforma,
      produtos: lote.produtos,
      fotos: {
        prontas: doLote.filter((foto) => foto.situacao === "pronta").length,
        naFila: doLote.filter((foto) => foto.situacao === "pendente" || foto.situacao === "baixando").length,
        falharam: doLote.filter((foto) => foto.situacao === "falhou").length,
      },
      falhas: doLote
        .filter((foto) => foto.situacao === "falhou")
        .slice(0, 5)
        .map((foto) => ({ endereco: foto.endereco, erro: foto.erro })),
    };
  });
}

/* ------------------------------------------------------------------ painel */

export type ProdutoDoLote = { id: string; nome: string; foto: string | null; preco: number; sobConsulta: boolean; naLoja: boolean };

export type ImportacaoNaLista = {
  id: string;
  quando: string;
  origem: string | null;
  plataforma: string | null;
  /* O nome da conexão ou chave que trouxe (Claude, ChatGPT). */
  por: string | null;
  fotos: { prontas: number; naFila: number; falharam: number };
  produtos: ProdutoDoLote[];
};

/**
 * As últimas importações da empresa, com os produtos de cada uma como estão
 * agora (o que foi apagado no painel some da lista) e o andamento das fotos.
 */
export async function listarImportacoes(admin: SupabaseClient, empresa: { companyId: string; tenantIds: string[] }, quantas = 10): Promise<ImportacaoNaLista[]> {
  /* Três idas ao banco (o banco fica longe): os lotes; as fotos e os itens
     juntos; os produtos. */
  const { data: brutos, error } = await admin
    .from("importacoes")
    .select("id,criada_em,origem,plataforma,chaves_de_acesso(nome)")
    .eq("company_id", empresa.companyId)
    .order("criada_em", { ascending: false })
    .limit(quantas);
  if (error) throw error;
  const lotes = (brutos ?? []) as Array<{
    id: string;
    criada_em: string;
    origem: string | null;
    plataforma: string | null;
    chaves_de_acesso?: { nome?: string | null } | Array<{ nome?: string | null }> | null;
  }>;
  if (!lotes.length) return [];
  const ids = lotes.map((lote) => lote.id);

  const [fotos, itens] = await Promise.all([
    admin.from("importacao_fotos").select("importacao_id,situacao").in("importacao_id", ids).limit(20_000),
    admin.from("importacao_itens").select("importacao_id,product_id").eq("company_id", empresa.companyId).in("importacao_id", ids).limit(5000),
  ]);
  if (fotos.error) throw fotos.error;
  if (itens.error) throw itens.error;
  const linhasDasFotos = (fotos.data ?? []) as Array<{ importacao_id: string; situacao: string }>;
  const linhasDosItens = (itens.data ?? []) as Array<{ importacao_id: string; product_id: string }>;

  const idsDosProdutos = [...new Set(linhasDosItens.map((item) => item.product_id))];
  const produtos = new Map<string, ProdutoDoLote>();
  const pedacos = [];
  for (let inicio = 0; inicio < idsDosProdutos.length; inicio += 200) {
    pedacos.push(
      admin
        .from("products")
        .select("id,name,image_url,price,price_on_request,show_in_catalog")
        .in("id", idsDosProdutos.slice(inicio, inicio + 200))
        .in("user_id", empresa.tenantIds)
    );
  }
  for (const { data } of await Promise.all(pedacos)) {
    for (const produto of (data ?? []) as Array<{ id: string; name: string; image_url: string | null; price: number | null; price_on_request: boolean | null; show_in_catalog: boolean | null }>) {
      produtos.set(produto.id, {
        id: produto.id,
        nome: produto.name,
        foto: produto.image_url,
        preco: Number(produto.price ?? 0),
        sobConsulta: produto.price_on_request === true,
        naLoja: produto.show_in_catalog === true,
      });
    }
  }

  return (
    lotes
      .map((lote) => {
        const chave = Array.isArray(lote.chaves_de_acesso) ? lote.chaves_de_acesso[0] : lote.chaves_de_acesso;
        const doLote = linhasDasFotos.filter((foto) => foto.importacao_id === lote.id);
        return {
          id: lote.id,
          quando: lote.criada_em,
          origem: lote.origem,
          plataforma: lote.plataforma,
          por: chave?.nome ?? null,
          fotos: {
            prontas: doLote.filter((foto) => foto.situacao === "pronta").length,
            naFila: doLote.filter((foto) => foto.situacao === "pendente" || foto.situacao === "baixando").length,
            falharam: doLote.filter((foto) => foto.situacao === "falhou").length,
          },
          produtos: linhasDosItens
            .filter((item) => item.importacao_id === lote.id)
            .map((item) => produtos.get(item.product_id))
            .filter((produto): produto is ProdutoDoLote => Boolean(produto)),
        };
      })
      /* Lote cujos produtos foram todos apagados não tem mais o que mostrar. */
      .filter((lote) => lote.produtos.length > 0)
  );
}

/** Os produtos de um lote desta empresa (os que ainda existem). null = lote de outra empresa. */
export async function produtosDoLote(admin: SupabaseClient, companyId: string, loteId: string): Promise<string[] | null> {
  const { data: lote } = await admin.from("importacoes").select("id").eq("id", loteId).eq("company_id", companyId).maybeSingle();
  if (!lote) return null;
  const { data, error } = await admin.from("importacao_itens").select("product_id").eq("importacao_id", loteId).eq("company_id", companyId);
  if (error) throw error;
  return ((data ?? []) as Array<{ product_id: string }>).map((item) => item.product_id);
}
