import { ApiV1Error } from "@/lib/api-v1/errors";
import { approveQuote } from "@/lib/api-v1/services/quotes-service";
import { duplicarOrcamento } from "@/lib/duplicar-orcamento.server";
import { mensagemDaTrava, notasQueTravamOPedido } from "@/lib/fiscal/servidor/travas";
import { mudarStatusDoPedido } from "@/lib/orders/mudar-status.server";

import type { ContextoDoAssistente } from "../contexto";
import { SITUACAO_DO_ORCAMENTO, SITUACAO_DO_PEDIDO, SITUACAO_FINANCEIRA, exigir, falha, nomeDoCliente, reais, recusar, um } from "./comum";
import { acharEtapa, ehVendaDoPdv, etapasDoQuadro, mensagemParaPessoa } from "./operacao-regras";
import type { FerramentaDeEscrita } from "./tipos";

/*
 * Pedidos e orçamentos: mudar a etapa da produção, cancelar, aprovar e
 * duplicar. Cada um pelo mesmo caminho do painel:
 * - etapa e cancelamento: lib/orders/mudar-status.server.ts (rota /api/orders/status);
 * - aprovar: approveQuote (rota /api/quotes/[id]/approve);
 * - duplicar: lib/duplicar-orcamento.server.ts (rota /api/quotes/[id]/duplicate).
 */

type ClienteEmbutido = { name?: string | null; company_name?: string | null };
type OrcamentoEmbutido = { number?: number | null; title?: string | null; internal_notes?: string | null };

type Pedido = {
  id: string;
  number: number | null;
  status: string;
  total_amount: number | null;
  paid_amount: number | null;
  financial_status: string | null;
  internal_notes: string | null;
  production_stage_id: string | null;
  clients?: ClienteEmbutido | ClienteEmbutido[] | null;
  quotes?: OrcamentoEmbutido | OrcamentoEmbutido[] | null;
};

const CAMPOS_DO_PEDIDO = {
  pedido_id: { tipo: "id" as const, descricao: "Id do pedido (de listar_pedidos)." },
  numero: { tipo: "inteiro" as const, descricao: "Ou o número do pedido, como aparece no painel.", min: 1 },
};

async function acharPedido(ctx: ContextoDoAssistente, args: Record<string, unknown>): Promise<Pedido> {
  if (!args.pedido_id && !args.numero) recusar("Informe pedido_id ou numero.");
  let consulta = ctx.supabase
    .from("orders")
    .select("id,number,status,total_amount,paid_amount,financial_status,internal_notes,production_stage_id,clients(name,company_name),quotes(number,title,internal_notes)")
    .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 Pedido[];
  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).");
  return lista[0];
}

function vendaDoPdv(pedido: Pedido) {
  const orcamento = um(pedido.quotes);
  return ehVendaDoPdv({ internal_notes: pedido.internal_notes, quoteTitle: orcamento?.title, quoteInternalNotes: orcamento?.internal_notes });
}

function descricaoDoPedido(pedido: Pedido) {
  return {
    numero: pedido.number,
    cliente: nomeDoCliente(um(pedido.clients)),
    titulo: um(pedido.quotes)?.title || null,
    total: reais(pedido.total_amount),
    pagamento: SITUACAO_FINANCEIRA[pedido.financial_status ?? ""] ?? pedido.financial_status,
  };
}

/* ------------------------------------------------------- mudar etapa */

const SITUACAO_PEDIDA: Record<string, string> = {
  aguardando_producao: "pending_production",
  em_producao: "in_production",
  pronto: "finished",
  entregue: "delivered",
};

type CargaDaEtapa = { id: string; numero: number | null; status: string; etapaId?: string | null; etapaNome: string | null };

export const mudarEtapaDoPedido: FerramentaDeEscrita<CargaDaEtapa> = {
  nome: "mudar_etapa_do_pedido",
  titulo: "Mudar a etapa de um pedido",
  descricao:
    "Muda a situação de um pedido na produção (aguardando produção, em produção, pronto, entregue) ou o põe numa das etapas do quadro da produção da empresa, pelo nome. Ir para em produção liga o cronômetro; sair dela pausa, como no painel. Não cancela: para isso use cancelar_pedido.",
  campos: {
    ...CAMPOS_DO_PEDIDO,
    situacao: { tipo: "opcao", descricao: "A nova situação.", opcoes: ["aguardando_producao", "em_producao", "pronto", "entregue"] },
    etapa: { tipo: "texto", descricao: "Ou o nome de uma etapa do quadro da produção (ex.: Impressão, Acabamento). A situação vem da etapa.", max: 60 },
  },
  tipo: "escrita",
  permissoes: ["orders.write"],
  async preparar(ctx, args) {
    exigir(ctx, "orders.write", "mudar a situação dos pedidos");
    if (!args.situacao && !args.etapa) recusar("Diga a nova situação (situacao) ou a etapa do quadro (etapa).");
    const pedido = await acharPedido(ctx, args);
    if (pedido.status === "canceled") recusar("Este pedido está cancelado e não volta para a produção.");
    if (vendaDoPdv(pedido)) recusar("Vendas do PDV não possuem fluxo de produção e não permitem alteração manual de status.");

    let status = args.situacao ? SITUACAO_PEDIDA[String(args.situacao)] : null;
    let etapaId: string | null | undefined;
    let etapaNome: string | null = null;
    if (args.etapa) {
      const { data, error } = await ctx.supabase.from("profiles").select("production_kanban_columns").eq("id", ctx.donoDosDados).maybeSingle();
      if (error) falha("ler as etapas da produção", error);
      const etapas = etapasDoQuadro((data as { production_kanban_columns?: unknown } | null)?.production_kanban_columns);
      const etapa = acharEtapa(etapas, String(args.etapa));
      if (!etapa) {
        recusar(
          etapas.length
            ? `Etapa não encontrada. As etapas do quadro são: ${etapas.map((e) => e.label).join(", ")}.`
            : "Esta empresa não criou etapas no quadro da produção. Use situacao (aguardando_producao, em_producao, pronto ou entregue)."
        );
      }
      if (status && status !== etapa.status) {
        recusar(`A etapa ${etapa.label} é de "${SITUACAO_DO_PEDIDO[etapa.status]}", e não de "${SITUACAO_DO_PEDIDO[status]}". Mande só a etapa.`);
      }
      status = etapa.status;
      etapaId = etapa.id;
      etapaNome = etapa.label;
    }
    if (!status) recusar("Situação inválida.");
    if (status === pedido.status && (etapaId === undefined || etapaId === pedido.production_stage_id)) {
      recusar(`O pedido ${pedido.number} já está ${etapaNome ? `na etapa ${etapaNome}` : `em "${SITUACAO_DO_PEDIDO[status]}"`}.`);
    }

    const avisos: string[] = [];
    if (pedido.status === "finished" && status === "pending_production") {
      avisos.push("O pedido sai de pronto e volta para a fila. O tempo de produção já apontado continua registrado.");
    }
    return {
      previa: {
        o_que_vai_acontecer: "A situação do pedido na produção será mudada.",
        pedido: descricaoDoPedido(pedido),
        de: SITUACAO_DO_PEDIDO[pedido.status] ?? pedido.status,
        para: etapaNome ? `${SITUACAO_DO_PEDIDO[status]} (etapa ${etapaNome})` : SITUACAO_DO_PEDIDO[status],
      },
      resumo: `Prévia: pedido ${pedido.number} para ${etapaNome ?? SITUACAO_DO_PEDIDO[status]}`,
      avisos,
      /* Pronto e entregue mexem no estoque (as travas do banco dão baixa):
         passam pela aprovação da pessoa. */
      sensivel: status === "delivered" || status === "finished",
      carga: { id: pedido.id, numero: pedido.number, status, ...(etapaId !== undefined ? { etapaId } : {}), etapaNome },
    };
  },
  async gravar(ctx, carga) {
    /* Entre a prévia e a gravação o pedido pode ter sido cancelado no painel:
       pedido cancelado não volta pela ferramenta. */
    const { data: agora, error: erroDeAgora } = await ctx.supabase
      .from("orders")
      .select("status")
      .eq("id", carga.id)
      .in("user_id", ctx.tenantIds)
      .maybeSingle();
    if (erroDeAgora) falha("conferir o pedido", erroDeAgora);
    if (!agora) recusar("Pedido não encontrado. Ele pode ter sido apagado no painel.");
    if ((agora as { status: string }).status === "canceled") {
      recusar(`O pedido ${carga.numero} foi cancelado e não volta pelo assistente. Para reabrir, use o painel, no próprio pedido.`);
    }
    const corpo: Record<string, unknown> = { id: carga.id, status: carga.status, source: "manual" };
    if (carga.etapaId !== undefined) {
      corpo.production_stage_id = carga.etapaId;
      corpo.production_stage_label = carga.etapaNome ?? "";
    }
    const resultado = await mudarStatusDoPedido(ctx.supabase, ctx.user, ctx.empresa, corpo);
    if (resultado.status !== 200) {
      recusar(mensagemParaPessoa(resultado.corpo.error, "Não foi possível mudar a situação do pedido agora. Tente de novo em instantes."));
    }
    const para = carga.etapaNome ? `${SITUACAO_DO_PEDIDO[carga.status]} (etapa ${carga.etapaNome})` : SITUACAO_DO_PEDIDO[carga.status];
    return {
      dados: { pedido_id: carga.id, numero: carga.numero, situacao: para },
      resumo: `Mudou o pedido ${carga.numero} para ${para}`,
    };
  },
};

/* ------------------------------------------------------- cancelar pedido */

type CargaDoCancelamento = { id: string; numero: number | null };

export const cancelarPedido: FerramentaDeEscrita<CargaDoCancelamento> = {
  nome: "cancelar_pedido",
  titulo: "Cancelar um pedido",
  descricao:
    "Cancela um pedido, como o botão do painel. Só cancela pedido sem pagamento registrado e sem nota fiscal valendo: se houver, a resposta diz o que fazer antes. Venda do PDV tem cancelamento próprio no painel.",
  campos: { ...CAMPOS_DO_PEDIDO, motivo: { tipo: "texto", descricao: "Por que está cancelando (fica só na conversa e no registro de atividade).", max: 300 } },
  tipo: "escrita",
  sobrescreve: true,
  sensivel: true,
  permissoes: ["orders.write"],
  async preparar(ctx, args) {
    exigir(ctx, "orders.write", "cancelar pedidos");
    const pedido = await acharPedido(ctx, args);
    if (pedido.status === "canceled") recusar(`O pedido ${pedido.number} já está cancelado.`);
    if (vendaDoPdv(pedido)) {
      recusar("Este pedido é uma venda do PDV. Ela é cancelada no painel, no próprio pedido, pelo botão Cancelar venda (que também cuida do estorno).");
    }

    /* As mesmas travas da rota, mostradas já na prévia. */
    const notas = await notasQueTravamOPedido(pedido.id);
    if (notas.length) recusar(mensagemDaTrava(notas, "cancelar"));
    const { count, error } = await ctx.supabase
      .from("payments")
      .select("id", { count: "exact", head: true })
      .eq("order_id", pedido.id)
      .in("user_id", ctx.tenantIds);
    if (error) falha("conferir os pagamentos do pedido", error);
    if ((count ?? 0) > 0) {
      recusar(
        `O pedido ${pedido.number} tem ${count === 1 ? "um pagamento registrado" : `${count} pagamentos registrados`}. Remova ou estorne os recebimentos no painel (no pedido, em Pagamentos) antes de cancelar.`
      );
    }

    return {
      previa: {
        o_que_vai_acontecer: "O pedido será cancelado. Ele sai da produção e para de contar nas vendas.",
        pedido: descricaoDoPedido(pedido),
        situacao_atual: SITUACAO_DO_PEDIDO[pedido.status] ?? pedido.status,
        ...(args.motivo ? { motivo: args.motivo } : {}),
      },
      resumo: `Prévia: cancelar o pedido ${pedido.number}`,
      avisos: [],
      carga: { id: pedido.id, numero: pedido.number },
    };
  },
  async gravar(ctx, carga) {
    const resultado = await mudarStatusDoPedido(ctx.supabase, ctx.user, ctx.empresa, { id: carga.id, status: "canceled", source: "manual" });
    if (resultado.status !== 200) {
      recusar(mensagemParaPessoa(resultado.corpo.error, "Não foi possível cancelar o pedido agora. Tente de novo em instantes."));
    }
    return { dados: { cancelado: true, pedido_id: carga.id, numero: carga.numero }, resumo: `Cancelou o pedido ${carga.numero}` };
  },
};

/* ------------------------------------------------------- orçamentos */

type Orcamento = {
  id: string;
  number: number | null;
  status: string | null;
  total: number | null;
  title: string | null;
  clients?: ClienteEmbutido | ClienteEmbutido[] | null;
};

const CAMPOS_DO_ORCAMENTO = {
  orcamento_id: { tipo: "id" as const, descricao: "Id do orçamento (de listar_orcamentos)." },
  numero: { tipo: "inteiro" as const, descricao: "Ou o número do orçamento, como aparece no painel.", min: 1 },
};

async function acharOrcamento(ctx: ContextoDoAssistente, args: Record<string, unknown>): Promise<Orcamento> {
  if (!args.orcamento_id && !args.numero) recusar("Informe orcamento_id ou numero.");
  let consulta = ctx.supabase.from("quotes").select("id,number,status,total,title,clients(name,company_name)").in("user_id", ctx.tenantIds).limit(2);
  consulta = args.orcamento_id ? consulta.eq("id", String(args.orcamento_id)) : consulta.eq("number", Number(args.numero));
  const { data, error } = await consulta;
  if (error) falha("ler o orçamento", error);
  const lista = (data ?? []) as unknown as Orcamento[];
  if (!lista.length) recusar("Orçamento não encontrado. Use listar_orcamentos para achar o número ou o id certo.");
  if (lista.length > 1) recusar("Há mais de um orçamento com esse número. Use o orcamento_id (de listar_orcamentos).");
  return lista[0];
}

function descricaoDoOrcamento(orcamento: Orcamento) {
  return {
    numero: orcamento.number,
    cliente: nomeDoCliente(um(orcamento.clients)),
    titulo: orcamento.title,
    total: reais(orcamento.total),
    situacao: SITUACAO_DO_ORCAMENTO[orcamento.status ?? ""] ?? orcamento.status,
  };
}

type CargaDoOrcamento = { id: string; numero: number | null };

export const aprovarOrcamento: FerramentaDeEscrita<CargaDoOrcamento> = {
  nome: "aprovar_orcamento",
  titulo: "Aprovar um orçamento e gerar o pedido",
  descricao:
    "Aprova um orçamento e gera o pedido de produção, como o botão Gerar pedido do painel. Depois disso o orçamento não pode mais ser excluído, só o pedido cancelado. Se faltar algo (assinatura do cliente, arte aprovada, estoque), a resposta diz o quê.",
  campos: { ...CAMPOS_DO_ORCAMENTO },
  tipo: "escrita",
  /* Vira pedido de produção e baixa o estoque: sempre com a aprovação da pessoa. */
  sensivel: true,
  permissoes: ["orders.write"],
  async preparar(ctx, args) {
    exigir(ctx, "orders.write", "gerar pedidos");
    const orcamento = await acharOrcamento(ctx, args);
    const { data: pedido, error } = await ctx.supabase.from("orders").select("id,number").eq("quote_id", orcamento.id).in("user_id", ctx.tenantIds).limit(1).maybeSingle();
    if (error) falha("conferir o orçamento", error);
    if (pedido) recusar(`Este orçamento já virou o pedido ${(pedido as { number: number | null }).number} (id ${(pedido as { id: string }).id}).`);
    const avisos = orcamento.status === "rejected" ? ["Este orçamento estava marcado como recusado pelo cliente."] : [];
    return {
      previa: { o_que_vai_acontecer: "O orçamento será aprovado e vira um pedido na produção.", orcamento: descricaoDoOrcamento(orcamento) },
      resumo: `Prévia: aprovar o orçamento ${orcamento.number}`,
      avisos,
      carga: { id: orcamento.id, numero: orcamento.number },
    };
  },
  async gravar(ctx, carga) {
    let resultado;
    try {
      resultado = await approveQuote({ supabase: ctx.supabase, user: ctx.user, quoteId: carga.id });
    } catch (erro) {
      if (erro instanceof ApiV1Error) {
        recusar(mensagemParaPessoa(erro.message, "Não foi possível gerar o pedido deste orçamento. Abra o orçamento no painel para ver o que falta."));
      }
      throw erro;
    }
    const numero = resultado.notification?.orderNumber ?? null;
    return {
      dados: { aprovado: true, orcamento_numero: carga.numero, pedido_id: resultado.orderId, pedido_numero: numero },
      resumo: `Aprovou o orçamento ${carga.numero} e gerou o pedido ${numero ?? ""}`.trim(),
    };
  },
};

export const duplicarOrcamentoFerramenta: FerramentaDeEscrita<CargaDoOrcamento> = {
  nome: "duplicar_orcamento",
  titulo: "Duplicar um orçamento",
  descricao:
    "Faz uma cópia de um orçamento (mesmo cliente, itens, preços e condições), como rascunho novo com o próximo número. Bom para repetir um pedido antigo. O link público não é copiado.",
  campos: { ...CAMPOS_DO_ORCAMENTO },
  tipo: "escrita",
  permissoes: ["quotes.write"],
  async preparar(ctx, args) {
    exigir(ctx, "quotes.write", "criar orçamentos");
    const orcamento = await acharOrcamento(ctx, args);
    return {
      previa: { o_que_vai_acontecer: "Uma cópia deste orçamento será criada como rascunho.", orcamento: descricaoDoOrcamento(orcamento) },
      resumo: `Prévia: duplicar o orçamento ${orcamento.number}`,
      avisos: [],
      carga: { id: orcamento.id, numero: orcamento.number },
    };
  },
  async gravar(ctx, carga) {
    const resultado = await duplicarOrcamento(ctx.supabase, ctx.user, ctx.empresa, carga.id);
    if (resultado.status !== 200) {
      recusar(mensagemParaPessoa(resultado.corpo.error, "Não foi possível duplicar o orçamento agora. Tente de novo em instantes."));
    }
    const novoNumero = Number(resultado.corpo.quoteNumber) || null;
    return {
      dados: { duplicado: true, orcamento_original: carga.numero, orcamento_id: resultado.corpo.quoteId, numero: novoNumero },
      resumo: `Duplicou o orçamento ${carga.numero} (novo: ${novoNumero ?? "?"})`,
    };
  },
};

export const FERRAMENTAS_DE_PEDIDOS_E_ORCAMENTOS = [mudarEtapaDoPedido, cancelarPedido, aprovarOrcamento, duplicarOrcamentoFerramenta];
