import { atualizarPeloOrcamento } from "@/lib/orders/atualizar-pelo-orcamento.server";
import { ehTextoDoSistema, normalizarObservacoes } from "@/lib/orders/observacoes";

import type { ContextoDoAssistente } from "../contexto";
import { SITUACAO_DO_PEDIDO, dataBr, dinheiro, exigir, falha, hojeEmBrasilia, nomeDoCliente, reais, recusar, um } from "./comum";
import { CAMPOS_DOS_CAMPOS_PROPRIOS, camposPropriosParaGravar } from "./orcamento-itens";
import { conferirContatos } from "./orcamentos";
import { mensagemParaPessoa } from "./operacao-regras";
import type { Argumentos, FerramentaDeEscrita } from "./tipos";

/*
 * Alterar um pedido: o que a tela do pedido (app/dashboard/orders/[id]) deixa
 * mudar, pelo mesmo caminho de cada botão.
 *
 * - prazo de entrega:    /api/orders/[id]/delivery-date
 * - observações:         /api/orders/[id]/observacoes (lib/orders/observacoes.ts)
 * - rastreio:            /api/orders/[id]/tracking (só pedido da loja virtual)
 * - campos próprios:     o bloco de campos próprios da tela
 * - atualizar pelo orçamento: lib/orders/atualizar-pelo-orcamento.server.ts
 *
 * A tela do pedido NÃO edita item, quantidade ou preço direto: quem muda é o
 * orçamento, e o pedido recebe pelo "Atualizar pelo orçamento" (os gatilhos de
 * estoque devolvem o que sai e baixam o que entra). Por isso aqui também é
 * assim: alterar_orcamento e depois atualizar_pelo_orcamento.
 */

type PedidoParaAlterar = {
  id: string;
  number: number | null;
  status: string;
  quote_id: string | null;
  total_amount: number | null;
  delivery_date: string | null;
  notes: string | null;
  internal_notes: string | null;
  campos_proprios: unknown;
  shipping_tracking_codes: string[] | null;
  shipping_tracking_url: string | null;
  shipping_tracking_notes: string | null;
  clients?: { name?: string | null; company_name?: string | null } | Array<{ name?: string | null; company_name?: string | null }> | null;
  quotes?: { number?: number | null; title?: string | null; internal_notes?: string | null; notes?: string | null; delivery_date?: string | null } | Array<{ number?: number | null; title?: string | null; internal_notes?: string | null; notes?: string | null; delivery_date?: string | null }> | null;
};

type CargaDoPedido = {
  id: string;
  numero: number | null;
  mudancas: Record<string, unknown>;
  sincronizar: boolean;
};

/* A regra da rota de rastreio: só pedido que veio da loja virtual. */
function veioDaLoja(pedido: PedidoParaAlterar) {
  const orcamento = um(pedido.quotes);
  const titulo = String(orcamento?.title ?? "").trim().toLowerCase();
  return (
    titulo.includes("pedido do catálogo") ||
    String(orcamento?.internal_notes ?? "").trim().toLowerCase().includes("origem:catalogo") ||
    String(pedido.internal_notes ?? "").trim().toLowerCase().includes("origem:catalogo")
  );
}

function codigosDeRastreio(valores: string[]) {
  const vistos = new Set<string>();
  return valores
    .join("\n")
    .split(/[\r\n,;]+/)
    .map((codigo) => codigo.trim())
    .filter((codigo) => {
      if (!codigo || vistos.has(codigo)) return false;
      vistos.add(codigo);
      return true;
    });
}

async function prepararPedido(ctx: ContextoDoAssistente, args: Argumentos) {
  exigir(ctx, "orders.edit", "editar pedidos");
  if (!args.pedido_id && !args.numero) recusar("Informe pedido_id ou numero.");
  let consulta = ctx.supabase
    .from("orders")
    .select(
      "id,number,status,quote_id,total_amount,delivery_date,notes,internal_notes,campos_proprios,shipping_tracking_codes,shipping_tracking_url,shipping_tracking_notes,clients(name,company_name),quotes(number,title,internal_notes,notes,delivery_date)"
    )
    .in("user_id", ctx.tenantIds)
    .limit(2);
  consulta = args.pedido_id ? consulta.eq("id", String(args.pedido_id)) : consulta.eq("number", Number(args.numero));
  const { data, error } = await consulta;
  if (error) falha("ler o pedido", error);
  const lista = (data ?? []) as unknown as PedidoParaAlterar[];
  if (!lista.length) recusar("Pedido não encontrado. Use listar_pedidos para achar o número ou o id certo.");
  if (lista.length > 1) recusar("Há mais de um pedido com esse número. Use o pedido_id (de listar_pedidos).");
  const pedido = lista[0];

  const mudancas: Record<string, unknown> = {};
  const previa: Record<string, unknown> = {};
  const avisos: string[] = [];

  /* Prazo de entrega. */
  if (args.entrega !== undefined || args.limpar_entrega === true) {
    if (args.entrega !== undefined && args.limpar_entrega === true) recusar("Mande entrega ou limpar_entrega, não os dois.");
    const nova = args.limpar_entrega === true ? null : String(args.entrega);
    const jaSaiu = ["delivered", "finished", "canceled"].includes(pedido.status);
    if (nova && nova < hojeEmBrasilia() && !jaSaiu) recusar("Esse pedido ainda não foi entregue — o prazo não pode ficar no passado.");
    const atual = pedido.delivery_date ?? um(pedido.quotes)?.delivery_date ?? null;
    if (nova !== atual) {
      mudancas.delivery_date = nova;
      previa.entrega = { de: atual ? dataBr(atual) : null, para: nova ? dataBr(nova) : "sem prazo" };
    }
  }

  /* Observações (as que vão para a nota fiscal). "" apaga sem trazer de volta as do orçamento. */
  if (args.observacoes !== undefined || args.limpar_observacoes === true) {
    if (args.observacoes !== undefined && args.limpar_observacoes === true) recusar("Mande observacoes ou limpar_observacoes, não os dois.");
    const nova = args.limpar_observacoes === true ? "" : normalizarObservacoes(args.observacoes);
    const doOrcamento = um(pedido.quotes)?.notes ?? null;
    const atual = pedido.notes ?? (doOrcamento && !ehTextoDoSistema(doOrcamento) ? doOrcamento : "");
    conferirContatos([nova], "na tela do pedido", atual);
    if (nova !== (pedido.notes ?? null)) {
      mudancas.notes = nova;
      previa.observacoes = { de: atual || null, para: nova || "(sem observações)" };
    }
  }

  /* Rastreio. */
  const mexeNoRastreio = args.rastreio_codigos !== undefined || args.rastreio_link !== undefined || args.rastreio_observacao !== undefined || args.limpar_rastreio === true;
  if (mexeNoRastreio) {
    if (!veioDaLoja(pedido)) recusar("O rastreio manual só existe para pedidos da loja virtual, como na tela do pedido.");
    const codigos = args.limpar_rastreio === true ? [] : args.rastreio_codigos !== undefined ? codigosDeRastreio(args.rastreio_codigos as string[]) : (pedido.shipping_tracking_codes ?? []);
    const linkBruto = args.limpar_rastreio === true ? "" : args.rastreio_link !== undefined ? String(args.rastreio_link).trim() : (pedido.shipping_tracking_url ?? "");
    const link = linkBruto ? (/^https?:\/\//i.test(linkBruto) ? linkBruto : `https://${linkBruto}`) : null;
    const observacao = args.limpar_rastreio === true ? null : args.rastreio_observacao !== undefined ? String(args.rastreio_observacao).trim() || null : (pedido.shipping_tracking_notes ?? null);
    conferirContatos([link, observacao], "na tela do pedido", [pedido.shipping_tracking_url, pedido.shipping_tracking_notes].filter(Boolean).join("\n"));
    const temRastreio = codigos.length > 0 || Boolean(link) || Boolean(observacao);
    mudancas.shipping_tracking_codes = codigos.length ? codigos : null;
    mudancas.shipping_tracking_url = link;
    mudancas.shipping_tracking_notes = observacao;
    mudancas.shipping_tracking_updated_at = temRastreio ? "agora" : null;
    previa.rastreio = temRastreio ? { codigos, link, observacao } : "removido";
  }

  /* Campos próprios. */
  if (args.campos !== undefined) {
    const campos = await camposPropriosParaGravar(ctx, "pedido", args.campos as Array<{ campo: string; valor?: string }>, pedido.campos_proprios);
    if (campos && Object.keys(campos.mudou).length) {
      mudancas.campos_proprios = campos.valores;
      previa.campos_proprios = campos.mudou;
    }
  }

  /* Atualizar pelo orçamento: a prévia é a mesma que a tela mostra. */
  const sincronizar = args.atualizar_pelo_orcamento === true;
  if (sincronizar) {
    if (!pedido.quote_id) recusar("Este pedido não tem orçamento de origem para atualizar.");
    const vista = await atualizarPeloOrcamento({ supabase: ctx.supabase, userId: ctx.user.id, tenantIds: ctx.tenantIds, orderId: pedido.id, mode: "preview" });
    if (vista.status !== 200) recusar(mensagemParaPessoa(String(vista.corpo.error ?? ""), "Não foi possível comparar o pedido com o orçamento agora."));
    const resumo = vista.corpo.summary as { added: number; removed: number; changed: number; totalChanges: number; totalChanged: boolean; totalBefore: number; totalAfter: number };
    if (!resumo.totalChanges && !resumo.totalChanged) recusar("O pedido já está igual ao orçamento: não há o que atualizar.");
    previa.atualizar_pelo_orcamento = {
      itens_novos: resumo.added,
      itens_que_saem: resumo.removed,
      itens_alterados: resumo.changed,
      total: { de: dinheiro(resumo.totalBefore), para: dinheiro(resumo.totalAfter) },
    };
    if (pedido.status === "in_production" && resumo.totalChanges) {
      avisos.push("O pedido está em produção: os itens são trocados, o estoque é devolvido e baixado de novo, e os apontamentos de produção perdem o vínculo com os itens antigos (a tela avisa o mesmo).");
    }
  }

  if (!Object.keys(mudancas).length && !sincronizar) recusar("Nada para mudar: mande o que muda (entrega, observacoes, rastreio, campos ou atualizar_pelo_orcamento).");

  return {
    previa: {
      o_que_vai_acontecer: `O pedido nº ${pedido.number} (${nomeDoCliente(um(pedido.clients)) ?? "cliente"}, ${SITUACAO_DO_PEDIDO[pedido.status] ?? pedido.status}) vai mudar.`,
      ...previa,
    },
    resumo: `Prévia da alteração do pedido nº ${pedido.number}`,
    avisos,
    carga: { id: pedido.id, numero: pedido.number, mudancas, sincronizar },
  };
}

export const alterarPedido: FerramentaDeEscrita<CargaDoPedido> = {
  nome: "alterar_pedido",
  titulo: "Alterar um pedido",
  descricao:
    "Altera o que a tela do pedido deixa mudar: prazo de entrega, observações (as que saem na nota fiscal), rastreio (só pedido da loja virtual), campos próprios e \"Atualizar pelo orçamento\". A tela do pedido não edita itens, opções, quantidades nem preços direto: para isso, mude o orçamento com alterar_orcamento e depois chame aqui com atualizar_pelo_orcamento (troca itens e valor do pedido pelos do orçamento; só em aguardando produção ou em produção, e nunca com nota fiscal valendo). A etapa da produção muda em mudar_etapa_do_pedido; cancelar, em cancelar_pedido.",
  campos: {
    pedido_id: { tipo: "id", descricao: "Id do pedido (de listar_pedidos)." },
    numero: { tipo: "inteiro", descricao: "Ou o número do pedido, como aparece no painel.", min: 1 },
    entrega: { tipo: "data", descricao: "Novo prazo de entrega." },
    limpar_entrega: { tipo: "booleano", descricao: "Tira o prazo de entrega." },
    observacoes: { tipo: "texto", descricao: "Observações do pedido (substituem as atuais; vão para a nota fiscal).", max: 1500 },
    limpar_observacoes: { tipo: "booleano", descricao: "Apaga as observações do pedido (as do orçamento não voltam)." },
    rastreio_codigos: { tipo: "textos", descricao: "Códigos de rastreio (substituem os atuais).", maxItens: 20, max: 80 },
    rastreio_link: { tipo: "texto", descricao: "Link para acompanhar a entrega.", max: 500 },
    rastreio_observacao: { tipo: "texto", descricao: "Observação do rastreio.", max: 500 },
    limpar_rastreio: { tipo: "booleano", descricao: "Apaga o rastreio." },
    campos: { tipo: "lista", descricao: "Campos próprios do pedido (valor vazio apaga).", itens: CAMPOS_DOS_CAMPOS_PROPRIOS, maxItens: 40 },
    atualizar_pelo_orcamento: { tipo: "booleano", descricao: "Troca itens e valor do pedido pelos do orçamento de origem (depois de alterar_orcamento)." },
  },
  tipo: "escrita",
  sobrescreve: true,
  /* Mexe em itens, valor e estoque do pedido: sempre com a prévia. */
  sensivel: (args) => args.atualizar_pelo_orcamento === true,
  permissoes: ["orders.edit"],
  preparar: prepararPedido,
  async gravar(ctx, carga) {
    const feito: string[] = [];
    if (Object.keys(carga.mudancas).length) {
      const mudancas = { ...carga.mudancas } as Record<string, unknown>;
      if (mudancas.shipping_tracking_updated_at === "agora") mudancas.shipping_tracking_updated_at = new Date().toISOString();
      /* As rotas de prazo e de observação marcam updated_at; a de rastreio não. */
      if (["delivery_date", "notes", "campos_proprios"].some((chave) => chave in mudancas)) mudancas.updated_at = new Date().toISOString();
      const { data, error } = await ctx.supabase.from("orders").update(mudancas).eq("id", carga.id).in("user_id", ctx.tenantIds).select("id");
      if (error) falha("alterar o pedido", error);
      if (!data || !data.length) recusar("O pedido não foi alterado: esta pessoa não tem permissão para mudar este pedido.");
      feito.push(...Object.keys(carga.mudancas).filter((chave) => !chave.startsWith("shipping_tracking_") || chave === "shipping_tracking_codes"));
    }
    let atualizacao: Record<string, unknown> | null = null;
    if (carga.sincronizar) {
      const resultado = await atualizarPeloOrcamento({ supabase: ctx.supabase, userId: ctx.user.id, tenantIds: ctx.tenantIds, orderId: carga.id, mode: "apply" });
      if (resultado.status !== 200) recusar(mensagemParaPessoa(String(resultado.corpo.error ?? ""), "Não foi possível atualizar o pedido pelo orçamento agora."));
      const resumo = resultado.corpo.summary as { totalAfter?: number } | undefined;
      atualizacao = { mensagem: resultado.corpo.message, total: dinheiro(resumo?.totalAfter) };
    }
    return {
      dados: {
        alterado: true,
        pedido_id: carga.id,
        numero: carga.numero,
        ...(feito.length ? { campos_mudados: feito } : {}),
        ...(atualizacao ? { atualizado_pelo_orcamento: atualizacao } : {}),
      },
      resumo: `Alterou o pedido nº ${carga.numero}${atualizacao ? ` (atualizado pelo orçamento: ${reais(atualizacao.total)})` : ""}`,
    };
  },
};

export const FERRAMENTAS_DE_PEDIDO_COMPLETO = [alterarPedido];
