import type { AmbienteFiscal } from "@/lib/fiscal/ambiente";
import {
  ambienteDaNota,
  emissorParaNota,
  exigenciasDaCidade,
  lerNfce,
  lerNfse,
  lerPreferencias,
  modeloAtivo,
  nfseNoPadraoNacional,
  problemaNaIdentificacaoDoCsc,
  problemaNoCsc,
  type AmbienteDaNota,
  type EmissorGuardado,
  type Presenca,
} from "@/lib/fiscal/emissor";
import {
  documentoValido,
  montarNotaDeProduto,
  montarNotaDeServico,
  montarPagamentos,
  pendenciasDaNotaDeProduto,
  pendenciasDaNotaDeServico,
  planejarNotas,
  soDigitos,
  temBloqueio,
  type DestinatarioDaNota,
  type ItemDaNota,
  type NotaMontada,
  type OpcaoDoItem,
  type Pendencia,
  type PedidoDaNota,
  type PerfilFiscal,
  type PlanoDoPedido,
  type ServicoDoCatalogo,
} from "@/lib/fiscal/montar";
import { explicarRejeicao } from "@/lib/fiscal/rejeicoes";
import { observacoesDoPedido } from "@/lib/orders/observacoes";
import { APARENCIA_DO_STATUS, podeMudarStatus, statusDaSpedyParaLocal, type StatusDaNota } from "@/lib/fiscal/status";
import {
  baixarArquivoDaNota,
  cancelarNota,
  cartaDeCorrecao,
  consultarNaAutoridade,
  criarNota,
  NOME_DO_MODELO,
  obterNota,
  procurarNotaPorIntegracao,
  reenviarEmail,
  type ModeloFiscal,
  type SpedyNota,
} from "@/lib/spedy/api";
import { ErroDaSpedy } from "@/lib/spedy/cliente";

import {
  agendarTarefa,
  carregarEmissor,
  regrasDaPlataforma,
  registrarEvento,
  saldoDoMes,
  segredosDoEmissor,
  spedyDoEmissor,
  type Admin,
  type SaldoDoMes,
} from "./base";
import { assinaturaDaEmpresa } from "./assinatura-da-empresa";

export const BUCKET_FISCAL = "documentos-fiscais";

export type OrigemDaNota = "pedido" | "pdv" | "loja" | "teste" | "avulsa" | "devolucao";

export type NotaGuardada = {
  id: string;
  empresa_id: string;
  ambiente: AmbienteFiscal;
  emissor_id: string;
  modelo: ModeloFiscal;
  ambiente_nota: AmbienteDaNota;
  origem: OrigemDaNota;
  pedido_id: string | null;
  grupo: string;
  status: StatusDaNota;
  spedy_status: string | null;
  spedy_nota_id: string | null;
  numero: number | null;
  serie: string | null;
  chave: string | null;
  protocolo: string | null;
  autorizada_em: string | null;
  cancelada_em: string | null;
  cancelamento_motivo: string | null;
  cancelamento_protocolo: string | null;
  valor: number;
  destinatario: Record<string, unknown> | null;
  payload: Record<string, unknown> | null;
  resposta: unknown;
  mensagem: string | null;
  mensagem_codigo: string | null;
  conta_credito: boolean;
  credito: "nenhum" | "reservado" | "consumido" | "liberado";
  credito_competencia: string | null;
  xml_path: string | null;
  pdf_path: string | null;
  arquivos_em: string | null;
  emitida_por: string | null;
  cancelada_por: string | null;
  /** Nota sem pedido: o que foi digitado na tela, para corrigir, reenviar ou copiar. */
  formulario?: Record<string, unknown> | null;
  /** Devolução: a nota de venda que está sendo devolvida. */
  nota_origem_id?: string | null;
  created_at: string;
  updated_at: string;
};

export class ErroFiscal extends Error {
  status: number;
  extra?: Record<string, unknown>;
  constructor(mensagem: string, status = 400, extra?: Record<string, unknown>) {
    super(mensagem);
    this.name = "ErroFiscal";
    this.status = status;
    this.extra = extra;
  }
}

const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const objeto = (valor: unknown): Record<string, unknown> =>
  valor && typeof valor === "object" && !Array.isArray(valor) ? (valor as Record<string, unknown>) : {};
const texto = (valor: unknown) => (typeof valor === "string" ? valor.trim() : "");

/** As datas da Spedy vêm no horário de São Paulo, sem fuso. O banco guarda em UTC. */
export function dataDaSpedy(valor: string | null | undefined): string | null {
  const limpo = texto(valor);
  if (!limpo) return null;
  const comFuso = /([zZ]|[+-]\d{2}:?\d{2})$/.test(limpo) ? limpo : `${limpo}-03:00`;
  const data = new Date(comFuso);
  return Number.isFinite(data.getTime()) ? data.toISOString() : null;
}

/* ================================================================== */
/* Carregar o pedido                                                   */
/* ================================================================== */

export type PedidoCarregado = {
  pedido: PedidoDaNota;
  destinatario: DestinatarioDaNota | null;
  clienteId: string | null;
  clienteEhConsumidorAvulso: boolean;
  status: string;
  origem: "pedido" | "pdv" | "loja" | "avulsa";
  servicosDoCatalogo: ServicoDoCatalogo[];
  perfis: PerfilFiscal[];
  produtos: Array<{ id: string; nome: string; ncm: string | null; fiscalTipo: string | null; perfilId: string | null }>;
};

type LinhaDeCliente = {
  id: string;
  name: string | null;
  company_name: string | null;
  type: string | null;
  document: string | null;
  email: string | null;
  phone: string | null;
  whatsapp: string | null;
  address_zip: string | null;
  address_street: string | null;
  address_number: string | null;
  address_complement: string | null;
  address_neighborhood: string | null;
  address_city: string | null;
  address_state: string | null;
  address_ibge: string | null;
  inscricao_estadual: string | null;
  contribuinte_icms: string | null;
  is_system_placeholder: boolean | null;
};

export function destinatarioDoCliente(cliente: LinhaDeCliente | null): DestinatarioDaNota | null {
  if (!cliente) return null;
  const documento = soDigitos(cliente.document);
  const tipoDoc = documentoValido(documento);
  const pj = cliente.type === "pj" || tipoDoc === "cnpj";
  const contribuinte = ["contribuinte", "isento", "nao_contribuinte"].includes(String(cliente.contribuinte_icms))
    ? (cliente.contribuinte_icms as DestinatarioDaNota["contribuinte"])
    : null;
  return {
    tipo: pj ? "pj" : "pf",
    nome: texto(pj ? cliente.company_name || cliente.name : cliente.name || cliente.company_name),
    documento: documento || null,
    email: texto(cliente.email) || null,
    telefone: soDigitos(cliente.phone || cliente.whatsapp) || null,
    inscricaoEstadual: texto(cliente.inscricao_estadual) || null,
    contribuinte,
    endereco: {
      cep: texto(cliente.address_zip) || null,
      logradouro: texto(cliente.address_street) || null,
      numero: texto(cliente.address_number) || null,
      complemento: texto(cliente.address_complement) || null,
      bairro: texto(cliente.address_neighborhood) || null,
      cidade: texto(cliente.address_city) || null,
      uf: texto(cliente.address_state).toUpperCase() || null,
      ibge: soDigitos(cliente.address_ibge) || null,
    },
  };
}

function lerOpcoes(selecionadas: unknown): OpcaoDoItem[] {
  const lista = objeto(selecionadas).options;
  if (!Array.isArray(lista)) return [];
  return lista
    .map((bruto) => {
      const opcao = objeto(bruto);
      const optionId = texto(opcao.option_id) || null;
      return {
        optionId,
        grupo: texto(opcao.attribute_name),
        rotulo: texto(opcao.label),
        preco: Number(opcao.price) || 0,
        price_unit: texto(opcao.price_unit) || null,
        price_applies_per_quantity: typeof opcao.price_applies_per_quantity === "boolean" ? opcao.price_applies_per_quantity : null,
      } satisfies OpcaoDoItem;
    })
    .filter((opcao) => opcao.rotulo || opcao.optionId);
}

export async function carregarPedido(admin: Admin, empresaId: string, pedidoId: string): Promise<PedidoCarregado> {
  if (!UUID.test(pedidoId)) throw new ErroFiscal("Pedido inválido.", 400);

  const { data: ordem, error } = await admin
    .from("orders")
    .select(
      "id,user_id,number,status,total_amount,created_at,delivery_date,client_id,payment_terms,internal_notes,notes,quotes(title,internal_notes,notes,shipping,discount)"
    )
    .eq("id", pedidoId)
    .eq("user_id", empresaId)
    .maybeSingle();
  if (error) throw new ErroFiscal(`Falha ao ler o pedido: ${error.message}`, 500);
  if (!ordem) throw new ErroFiscal("Pedido não encontrado.", 404);

  const orcamentoBruto = (ordem as Record<string, unknown>).quotes;
  const orcamento = objeto(Array.isArray(orcamentoBruto) ? orcamentoBruto[0] : orcamentoBruto);

  const [itensR, pagamentosR, clienteR, lojaR, perfisR] = await Promise.all([
    admin
      .from("order_items")
      .select("id,product_id,title,quantity,unit_type,width_m,height_m,length_m,total_price,unit_price,selected_options,fiscal_ncm,created_at")
      .eq("order_id", pedidoId)
      .order("created_at", { ascending: true }),
    admin
      .from("payments")
      .select("amount,payment_method,payment_date,created_at")
      .eq("order_id", pedidoId)
      .is("reversed_at", null)
      .order("payment_date", { ascending: true }),
    ordem.client_id
      ? admin
          .from("clients")
          .select(
            "id,name,company_name,type,document,email,phone,whatsapp,address_zip,address_street,address_number,address_complement,address_neighborhood,address_city,address_state,address_ibge,inscricao_estadual,contribuinte_icms,is_system_placeholder"
          )
          .eq("id", ordem.client_id)
          .maybeSingle()
      : Promise.resolve({ data: null, error: null }),
    admin.from("catalog_orders").select("id,cart_payload").eq("approved_order_id", pedidoId).limit(1).maybeSingle(),
    admin.from("fiscal_perfis").select("id,nome,tipo,padrao,ativo,regras").eq("empresa_id", empresaId),
  ]);
  if (itensR.error) throw new ErroFiscal(`Falha ao ler os itens: ${itensR.error.message}`, 500);

  const linhas = (itensR.data ?? []) as Array<Record<string, unknown>>;
  const produtoIds = [...new Set(linhas.map((l) => texto(l.product_id)).filter((id) => UUID.test(id)))];
  const opcaoIds = [
    ...new Set(
      linhas.flatMap((l) => lerOpcoes(l.selected_options).map((o) => o.optionId ?? "")).filter((id) => UUID.test(id))
    ),
  ];

  const [produtosR, servicosR] = await Promise.all([
    produtoIds.length
      ? admin.from("products").select("id,name,sku,barcode,fiscal_tipo,ncm,cest,fiscal_origem,fiscal_perfil_id,product_kind").in("id", produtoIds)
      : Promise.resolve({ data: [], error: null }),
    opcaoIds.length
      ? admin.from("optional_services").select("id,title,fiscal_tipo,fiscal_perfil_id").eq("user_id", empresaId).in("id", opcaoIds)
      : Promise.resolve({ data: [], error: null }),
  ]);

  const produtos = new Map(((produtosR.data ?? []) as Array<Record<string, unknown>>).map((p) => [String(p.id), p]));

  /*
   * Item digitado à mão no orçamento (sem produto do catálogo): se a empresa
   * tem um produto com exatamente o mesmo nome, a nota usa os dados fiscais
   * dele (NCM, CEST, origem, tributação). Só quando o nome bate com um
   * produto só: dois produtos com o mesmo nome não adivinham nada.
   */
  const titulosSemProduto = [
    ...new Set(linhas.filter((l) => !UUID.test(texto(l.product_id))).map((l) => texto(l.title)).filter((t) => t.length >= 2)),
  ].slice(0, 200);
  const porNome = new Map<string, Record<string, unknown>>();
  if (titulosSemProduto.length) {
    const { data: mesmos } = await admin
      .from("products")
      .select("id,name,sku,barcode,fiscal_tipo,ncm,cest,fiscal_origem,fiscal_perfil_id,product_kind")
      .eq("user_id", empresaId)
      .in("name", titulosSemProduto)
      .limit(400);
    const contagem = new Map<string, number>();
    for (const p of (mesmos ?? []) as Array<Record<string, unknown>>) contagem.set(texto(p.name), (contagem.get(texto(p.name)) ?? 0) + 1);
    for (const p of (mesmos ?? []) as Array<Record<string, unknown>>) {
      if (contagem.get(texto(p.name)) === 1) {
        porNome.set(texto(p.name), p);
        produtos.set(String(p.id), p);
      }
    }
  }

  const itens: ItemDaNota[] = linhas.map((linha) => {
    const produto = produtos.get(texto(linha.product_id)) ?? porNome.get(texto(linha.title));
    const origem = produto?.fiscal_origem;
    return {
      id: String(linha.id),
      titulo: texto(linha.title) || texto(produto?.name) || "Produto",
      produtoId: produto ? String(produto.id) : null,
      sku: texto(produto?.sku) || null,
      codigoDeBarras: texto(produto?.barcode) || null,
      unitType: texto(linha.unit_type) || null,
      quantidade: Number(linha.quantity) || 1,
      larguraM: linha.width_m === null || linha.width_m === undefined ? null : Number(linha.width_m),
      alturaM: linha.height_m === null || linha.height_m === undefined ? null : Number(linha.height_m),
      comprimentoM: linha.length_m === null || linha.length_m === undefined ? null : Number(linha.length_m),
      valorTotal: Number(linha.total_price ?? Number(linha.quantity) * Number(linha.unit_price)) || 0,
      opcoes: lerOpcoes(linha.selected_options),
      /* Produto digital (arquivo, curso, código) é serviço: sai na NFS-e, nunca na NF-e de mercadoria. */
      fiscalTipo:
        produto?.fiscal_tipo === "servico" || (produto?.product_kind === "digital" && produto?.fiscal_tipo !== "mercadoria")
          ? "servico"
          : produto?.fiscal_tipo === "mercadoria"
            ? "mercadoria"
            : null,
      /* O NCM informado no próprio item (pela janela da nota) vale mais que o do produto. */
      ncm: texto(linha.fiscal_ncm) || texto(produto?.ncm) || null,
      cest: texto(produto?.cest) || null,
      origem: origem === null || origem === undefined ? null : Number(origem),
      perfilId: texto(produto?.fiscal_perfil_id) || null,
    };
  });

  const carrinho = objeto(objeto(lojaR.data).cart_payload);
  const frete = objeto(carrinho.shipping);
  const opcaoDeFrete = objeto(frete.option);
  const provedorDoFrete = texto(opcaoDeFrete.provider || frete.provider).toLowerCase();
  const valorDoFrete = Number(frete.amount ?? opcaoDeFrete.price ?? 0) || 0;
  const freteDoOrcamento = Math.max(0, Number(orcamento.shipping ?? 0) || 0);
  const temEntrega = (valorDoFrete > 0 && provedorDoFrete !== "pickup") || freteDoOrcamento > 0;

  const notasInternas = `${texto(ordem.internal_notes)} ${texto(orcamento.internal_notes)} ${texto(orcamento.title)}`.toLowerCase();
  const origem: PedidoCarregado["origem"] = notasInternas.includes("origem:pdv") || notasInternas.includes("venda pdv")
    ? "pdv"
    : lojaR.data
      ? "loja"
      : "pedido";

  const cliente = (clienteR.data ?? null) as LinhaDeCliente | null;

  const pedido: PedidoDaNota = {
    id: String(ordem.id),
    numero: ordem.number === null || ordem.number === undefined ? null : Number(ordem.number),
    total: Number(ordem.total_amount ?? 0) || 0,
    criadoEm: texto(ordem.created_at) || null,
    entregaEm: texto(ordem.delivery_date) || null,
    itens,
    freteDoOrcamento: valorDoFrete > 0 && freteDoOrcamento === 0 ? valorDoFrete : freteDoOrcamento,
    descontoDoOrcamento: Math.max(0, Number(orcamento.discount ?? 0) || 0),
    temEntrega,
    pagamentos: ((pagamentosR.data ?? []) as Array<Record<string, unknown>>).map((p) => ({
      metodo: texto(p.payment_method),
      valor: Number(p.amount) || 0,
    })),
    condicoes: ordem.payment_terms ?? null,
    observacoes:
      observacoesDoPedido(
        { notes: typeof ordem.notes === "string" ? ordem.notes : null },
        { notes: typeof orcamento.notes === "string" ? orcamento.notes : null }
      ).texto || null,
  };

  return {
    pedido,
    destinatario: cliente && !cliente.is_system_placeholder ? destinatarioDoCliente(cliente) : null,
    clienteId: cliente?.id ?? null,
    clienteEhConsumidorAvulso: Boolean(cliente?.is_system_placeholder),
    status: texto(ordem.status),
    origem,
    servicosDoCatalogo: ((servicosR.data ?? []) as Array<Record<string, unknown>>).map((s) => ({
      id: String(s.id),
      titulo: texto(s.title),
      fiscalTipo: s.fiscal_tipo === "servico" ? "servico" : s.fiscal_tipo === "produto" ? "produto" : null,
      perfilId: texto(s.fiscal_perfil_id) || null,
    })),
    perfis: ((perfisR.data ?? []) as Array<Record<string, unknown>>).map((p) => ({
      id: String(p.id),
      nome: texto(p.nome),
      tipo: p.tipo === "servico" ? "servico" : "mercadoria",
      padrao: p.padrao === true,
      ativo: p.ativo !== false,
      regras: p.regras,
    })),
    produtos: [...produtos.values()].map((p) => ({
      id: String(p.id),
      nome: texto(p.name),
      ncm: texto(p.ncm) || null,
      fiscalTipo: texto(p.fiscal_tipo) || null,
      perfilId: texto(p.fiscal_perfil_id) || null,
    })),
  };
}

/* ================================================================== */
/* Planejar (o que a gaveta "Emitir nota" mostra)                      */
/* ================================================================== */

export type EscolhasDaEmissao = {
  modeloDeMercadoria?: "nfe" | "nfce" | null;
  consumidorFinal?: boolean | null;
  presenca?: Presenca | null;
  observacao?: string | null;
  enviarEmail?: boolean | null;
  /** CPF/CNPJ digitado na hora (NFC-e do balcão). */
  documento?: string | null;
  /** Só estas notas (chaves do plano). Vazio = todas. */
  somente?: string[] | null;
};

export type NotaPlanejada = {
  chave: string;
  modelo: ModeloFiscal;
  grupo: string;
  titulo: string;
  ambienteNota: AmbienteDaNota;
  contaCredito: boolean;
  montada: NotaMontada | null;
  pendencias: Pendencia[];
  existente: Pick<NotaGuardada, "id" | "modelo" | "status" | "numero" | "serie" | "mensagem" | "mensagem_codigo"> | null;
  /** Já tem nota viva deste grupo: não sai de novo. */
  jaEmitida: boolean;
};

export type PlanoDaEmissao = {
  pedidoId: string;
  numero: number | null;
  origem: PedidoCarregado["origem"];
  statusDoPedido: string;
  emissor: {
    existe: boolean;
    status: string | null;
    modelos: { nfe: boolean; nfce: boolean; nfse: boolean };
  };
  modeloDeMercadoria: "nfe" | "nfce" | null;
  consumidorFinal: boolean;
  presenca: Presenca;
  enviarEmail: boolean;
  /** As observações do pedido: é com elas que a gaveta abre o campo "Observação na nota". */
  observacoes: string;
  destinatario: DestinatarioDaNota | null;
  clienteId: string | null;
  notas: NotaPlanejada[];
  pendenciasGerais: Pendencia[];
  saldo: SaldoDoMes;
  notasQueGastam: number;
  totais: { pedido: number; subtotal: number; desconto: number; frete: number; outros: number };
};

const ATIVAS_NO_PEDIDO: StatusDaNota[] = ["preparando", "rascunho", "enviando", "processando", "autorizada", "contingencia", "cancelando", "rejeitada", "erro"];
const VIVAS: StatusDaNota[] = ["enviando", "processando", "autorizada", "contingencia", "cancelando"];

/** Nota de homologação num servidor de produção é teste: não gasta pacote. */
export function contaCredito(ambiente: AmbienteFiscal, ambienteNota: AmbienteDaNota, origem: OrigemDaNota) {
  if (origem === "teste") return false;
  if (ambiente === "producao" && ambienteNota !== "producao") return false;
  return true;
}

export function pendenciaDeSaldo(saldo: SaldoDoMes, notasQueGastam: number): Pendencia {
  return {
    codigo: "creditos.sem_saldo",
    nivel: "bloqueio",
    onde: "empresa",
    titulo: saldo.entradas === 0 ? "Nenhum pacote de notas neste mês" : "Notas do pacote acabaram",
    detalhe:
      saldo.entradas === 0
        ? "Compre um pacote em Assinatura → Notas fiscais para emitir."
        : `Esta emissão precisa de ${notasQueGastam} ${notasQueGastam === 1 ? "nota" : "notas"} e restam ${Math.max(0, saldo.disponiveis)}.`,
  };
}

export type PlanoCompleto = PlanoDaEmissao & { carregado: PedidoCarregado; plano: PlanoDoPedido; emissorGuardado: EmissorGuardado | null };

export type NotaExistente = Pick<NotaGuardada, "id" | "modelo" | "grupo" | "status" | "numero" | "serie" | "mensagem" | "mensagem_codigo">;

export async function planejarEmissao(
  admin: Admin,
  input: { empresaId: string; ambiente: AmbienteFiscal; pedidoId: string; escolhas?: EscolhasDaEmissao }
): Promise<PlanoCompleto> {
  const [carregado, emissor, saldo, existentesR] = await Promise.all([
    carregarPedido(admin, input.empresaId, input.pedidoId),
    carregarEmissor(admin, input.empresaId, input.ambiente),
    saldoDoMes(admin, input.empresaId, input.ambiente),
    admin
      .from("fiscal_notas")
      .select("id,modelo,grupo,status,numero,serie,mensagem,mensagem_codigo,created_at")
      .eq("pedido_id", input.pedidoId)
      .eq("empresa_id", input.empresaId)
      .eq("ambiente", input.ambiente)
      .in("status", ATIVAS_NO_PEDIDO)
      .order("created_at", { ascending: false }),
  ]);

  return planejarCarregado({
    ambiente: input.ambiente,
    carregado,
    emissor,
    saldo,
    existentes: (existentesR.data ?? []) as NotaExistente[],
    escolhas: input.escolhas ?? {},
  });
}

/**
 * O plano a partir do que já foi carregado: o pedido do sistema ou a nota
 * montada na hora, sem pedido. Não lê nem grava nada.
 */
export function planejarCarregado(input: {
  ambiente: AmbienteFiscal;
  carregado: PedidoCarregado;
  emissor: EmissorGuardado | null;
  saldo: SaldoDoMes;
  existentes: NotaExistente[];
  escolhas: EscolhasDaEmissao;
}): PlanoCompleto {
  const { carregado, emissor, saldo, escolhas } = input;
  const preferencias = lerPreferencias(emissor?.preferencias);
  const modelos = {
    nfe: Boolean(emissor && modeloAtivo(emissor, "nfe")),
    nfce: Boolean(emissor && modeloAtivo(emissor, "nfce")),
    nfse: Boolean(emissor && modeloAtivo(emissor, "nfse")),
  };

  let destinatario = carregado.destinatario;
  const documentoDigitado = soDigitos(escolhas.documento);
  if (documentoDigitado) {
    destinatario = destinatario
      ? { ...destinatario, documento: documentoDigitado }
      : {
          tipo: documentoDigitado.length === 14 ? "pj" : "pf",
          nome: "",
          documento: documentoDigitado,
          email: null,
          telefone: null,
          inscricaoEstadual: null,
          contribuinte: null,
          endereco: null,
        };
  }

  const plano = planejarNotas(carregado.pedido, { perfis: carregado.perfis, servicosDoCatalogo: carregado.servicosDoCatalogo });

  let modeloDeMercadoria: "nfe" | "nfce" | null = null;
  if (plano.mercadorias.length) {
    const pedido = escolhas.modeloDeMercadoria;
    if (pedido === "nfce" && modelos.nfce) modeloDeMercadoria = "nfce";
    else if (pedido === "nfe" && modelos.nfe) modeloDeMercadoria = "nfe";
    else if (carregado.origem === "pdv") modeloDeMercadoria = modelos.nfce ? "nfce" : modelos.nfe ? "nfe" : null;
    else modeloDeMercadoria = modelos.nfe ? "nfe" : modelos.nfce ? "nfce" : null;
  }

  const consumidorFinal = escolhas.consumidorFinal ?? preferencias.consumidorFinal;
  const presenca: Presenca =
    escolhas.presenca ?? (carregado.origem === "loja" ? "internet" : carregado.origem === "pdv" ? "presence" : preferencias.presencaPedido);
  const enviarEmail = escolhas.enviarEmail ?? preferencias.enviarEmail;
  /* O que vai nas informações complementares: o que foi escrito na gaveta —
     inclusive vazio, que é a pessoa tirando a observação desta nota — e, sem
     isso (emissão automática, reenvio), as observações do pedido. */
  const observacaoDaNota = typeof escolhas.observacao === "string" ? escolhas.observacao : carregado.pedido.observacoes ?? null;
  const pendenciasGerais: Pendencia[] = [];

  if (!emissor) {
    pendenciasGerais.push({
      codigo: "empresa.nao_configurada",
      nivel: "bloqueio",
      onde: "empresa",
      titulo: "Notas fiscais ainda não configuradas",
      detalhe: "Configure a empresa emissora em Notas fiscais → Configuração.",
    });
  } else if (emissor.status !== "ativo") {
    pendenciasGerais.push({
      codigo: "empresa.inativa",
      nivel: "bloqueio",
      onde: "empresa",
      titulo: emissor.status === "suspenso" ? "Emissão suspensa" : "Configuração não concluída",
      detalhe:
        emissor.status === "suspenso"
          ? "A emissão desta empresa foi suspensa. Fale com o suporte da Ktalogu."
          : "Termine a configuração em Notas fiscais → Configuração.",
    });
  }
  if (carregado.status === "canceled") {
    pendenciasGerais.push({ codigo: "pedido.cancelado", nivel: "bloqueio", onde: "pedido", titulo: "Pedido cancelado não emite nota" });
  }
  if (plano.inconsistente) {
    pendenciasGerais.push({
      codigo: "pedido.valores",
      nivel: "bloqueio",
      onde: "pedido",
      titulo: "Os valores não fecham",
      detalhe: "O total precisa ser maior que zero e o desconto não pode passar da soma dos itens.",
    });
  }
  if (plano.mercadorias.length && !modeloDeMercadoria && emissor) {
    pendenciasGerais.push({
      codigo: "empresa.sem_modelo_produto",
      nivel: "bloqueio",
      onde: "empresa",
      titulo: "Nota de produto e cupom fiscal desligados",
      detalhe: "Há produtos para a nota. Ligue a nota de produto ou o cupom fiscal na configuração.",
    });
  }
  if (modeloDeMercadoria === "nfce" && emissor) {
    const producaoDoCupom = ambienteDaNota(emissor, "nfce") === "producao";
    const configDoCupom = lerNfce(emissor.nfce);
    const segredos = segredosDoEmissor(emissor);
    const modo = producaoDoCupom ? "de uso real" : "de teste";
    const identificacao = producaoDoCupom ? configDoCupom.tokenIdProducao : configDoCupom.tokenIdHomologacao;
    const csc = producaoDoCupom ? segredos.cscProducao : segredos.cscHomologacao;
    const problema =
      !identificacao || !csc
        ? `Falta a identificação ou o CSC ${modo}.`
        : problemaNaIdentificacaoDoCsc(identificacao, `identificação do CSC ${modo}`) ?? problemaNoCsc(csc, `CSC ${modo} guardado`);
    if (problema) {
      pendenciasGerais.push({
        codigo: "empresa.csc",
        nivel: "bloqueio",
        onde: "empresa",
        titulo: "O código CSC do cupom fiscal precisa de correção",
        detalhe: `${problema} Corrija em Notas fiscais → Configuração, no passo Notas e impostos.`,
      });
    }
  }
  if (plano.servicos.length && !modelos.nfse && emissor) {
    pendenciasGerais.push({
      codigo: "empresa.sem_nfse",
      nivel: "bloqueio",
      onde: "empresa",
      titulo: "Nota de serviço desligada",
      detalhe: "Há serviços para a nota. Ligue a nota de serviço na configuração ou marque os itens como produto.",
    });
  }

  const existentes = input.existentes;
  const acharExistente = (modelo: ModeloFiscal, grupo: string) =>
    existentes.find((nota) => nota.grupo === grupo && (modelo === "nfse" ? nota.modelo === "nfse" : nota.modelo !== "nfse")) ?? null;

  const notas: NotaPlanejada[] = [];
  const emissorDaNota = emissor ? emissorParaNota(emissor) : null;

  if (plano.mercadorias.length && modeloDeMercadoria && emissor && emissorDaNota) {
    const ambienteNota = ambienteDaNota(emissor, modeloDeMercadoria);
    const existente = acharExistente(modeloDeMercadoria, "");
    const montada = montarNotaDeProduto({
      emissor: emissorDaNota,
      destinatario,
      pedido: carregado.pedido,
      linhas: plano.mercadorias,
      perfis: carregado.perfis,
      opcoes: {
        modelo: modeloDeMercadoria,
        integrationId: existente?.id ?? "00000000-0000-0000-0000-000000000000",
        emitir: true,
        homologacao: ambienteNota !== "producao",
        consumidorFinal,
        presenca,
        enviarEmail,
        numeroDoPedido: carregado.pedido.numero,
        observacao: observacaoDaNota,
      },
    });
    const pagamentos = montarPagamentos(carregado.pedido, montada.valor, { modelo: modeloDeMercadoria });
    const pendencias = pendenciasDaNotaDeProduto({
      modelo: modeloDeMercadoria,
      emissor: emissorDaNota,
      destinatario,
      linhas: plano.mercadorias,
      perfis: carregado.perfis,
      consumidorFinal,
      total: montada.valor,
      emAberto: pagamentos.emAberto,
      presenca,
      temEntrega: carregado.pedido.temEntrega,
    });
    if (montada.resumo.impostoDestacado > 0) {
      pendencias.push({
        codigo: "pedido.ipi",
        nivel: "aviso",
        onde: "perfil",
        titulo: "A nota soma IPI ao valor do pedido",
        detalhe: "O IPI destacado entra no total da nota e fica em aberto para o cliente.",
      });
    }
    notas.push({
      chave: modeloDeMercadoria,
      modelo: modeloDeMercadoria,
      grupo: "",
      titulo: `${NOME_DO_MODELO[modeloDeMercadoria]} dos produtos`,
      ambienteNota,
      contaCredito: contaCredito(input.ambiente, ambienteNota, carregado.origem),
      montada,
      pendencias,
      existente,
      jaEmitida: Boolean(existente && VIVAS.includes(existente.status)),
    });
  }

  if (plano.servicos.length && emissor && emissorDaNota && modelos.nfse) {
    const nfse = lerNfse(emissor.nfse);
    const exige = exigenciasDaCidade(nfse.cidade?.opcoes);
    const ambienteNota = ambienteDaNota(emissor, "nfse");
    for (const grupo of plano.servicos) {
      const grupoId = grupo.perfilId ?? "";
      const perfil = carregado.perfis.find((p) => p.id === grupo.perfilId);
      const existente = acharExistente("nfse", grupoId);
      const pendencias = pendenciasDaNotaDeServico({
        emissor: emissorDaNota,
        destinatario,
        grupo,
        perfis: carregado.perfis,
        exige,
      });
      const montada = perfil
        ? montarNotaDeServico({
            emissor: emissorDaNota,
            destinatario,
            grupo,
            perfil,
            opcoes: {
              integrationId: existente?.id ?? "00000000-0000-0000-0000-000000000000",
              emitir: true,
              enviarEmail,
              numeroDoPedido: carregado.pedido.numero,
              observacao: observacaoDaNota,
              exigeAnexo: exige.anexoSimples,
              padraoNacional: nfseNoPadraoNacional(nfse),
            },
          })
        : null;
      notas.push({
        chave: `nfse:${grupoId}`,
        modelo: "nfse",
        grupo: grupoId,
        titulo: plano.servicos.length > 1 && perfil ? `NFS-e · ${perfil.nome}` : "NFS-e dos serviços",
        ambienteNota,
        contaCredito: contaCredito(input.ambiente, ambienteNota, carregado.origem),
        montada,
        pendencias,
        existente,
        jaEmitida: Boolean(existente && VIVAS.includes(existente.status)),
      });
    }
  }

  const notasQueGastam = notas.filter((nota) => !nota.jaEmitida && nota.contaCredito).length;
  if (notasQueGastam > saldo.disponiveis && emissor) {
    pendenciasGerais.push(pendenciaDeSaldo(saldo, notasQueGastam));
  }

  return {
    pedidoId: carregado.pedido.id,
    numero: carregado.pedido.numero,
    origem: carregado.origem,
    statusDoPedido: carregado.status,
    emissor: { existe: Boolean(emissor), status: emissor?.status ?? null, modelos },
    modeloDeMercadoria,
    consumidorFinal,
    presenca,
    enviarEmail,
    observacoes: carregado.pedido.observacoes ?? "",
    destinatario,
    clienteId: carregado.clienteId,
    notas,
    pendenciasGerais,
    saldo,
    notasQueGastam,
    totais: {
      pedido: Math.round(carregado.pedido.total * 100),
      subtotal: plano.subtotal,
      desconto: plano.desconto,
      frete: plano.frete,
      outros: plano.outros,
    },
    carregado,
    plano,
    emissorGuardado: emissor,
  };
}

/* ================================================================== */
/* Emitir (ou gerar prévia)                                            */
/* ================================================================== */

export async function limiteDeTeste(admin: Admin, empresaId: string, ambiente: AmbienteFiscal) {
  if (ambiente !== "producao") return { usadas: 0, limite: Number.POSITIVE_INFINITY };
  const regras = await regrasDaPlataforma(admin);
  const inicio = new Date();
  inicio.setUTCDate(1);
  inicio.setUTCHours(0, 0, 0, 0);
  const { count } = await admin
    .from("fiscal_notas")
    .select("id", { count: "exact", head: true })
    .eq("empresa_id", empresaId)
    .eq("ambiente", ambiente)
    .neq("ambiente_nota", "producao")
    .not("status", "in", "(erro,removida)")
    .gte("created_at", inicio.toISOString());
  return { usadas: count ?? 0, limite: regras.notas_teste_por_mes };
}

export type ResultadoDaEmissao = {
  notas: Array<{ id: string; modelo: ModeloFiscal; status: StatusDaNota; mensagem: string | null }>;
  bloqueado: boolean;
  pendencias: Pendencia[];
};

export async function emitirDoPedido(
  admin: Admin,
  input: {
    empresaId: string;
    ambiente: AmbienteFiscal;
    pedidoId: string;
    usuarioId: string | null;
    escolhas?: EscolhasDaEmissao;
    previa?: boolean;
  }
): Promise<ResultadoDaEmissao> {
  const plano = await planejarEmissao(admin, input);
  return emitirPlano(admin, {
    empresaId: input.empresaId,
    ambiente: input.ambiente,
    usuarioId: input.usuarioId,
    escolhas: input.escolhas,
    previa: input.previa,
    plano,
    pedidoId: plano.pedidoId,
  });
}

/** Emite (ou gera a prévia de) as notas de um plano já montado, com ou sem pedido. */
export async function emitirPlano(
  admin: Admin,
  input: {
    empresaId: string;
    ambiente: AmbienteFiscal;
    usuarioId: string | null;
    escolhas?: EscolhasDaEmissao;
    previa?: boolean;
    plano: PlanoCompleto;
    pedidoId: string | null;
    /** Nota sem pedido: o id que a tela gerou, para dois cliques não virarem duas notas. */
    idDaNovaNota?: string | null;
    /** Nota sem pedido: o que foi digitado, guardado junto da nota. */
    formulario?: Record<string, unknown> | null;
  }
): Promise<ResultadoDaEmissao> {
  const { plano } = input;
  const somente = (input.escolhas?.somente ?? []).filter(Boolean);
  /* "nfse" sozinho escolhe todas as NFS-e do pedido (a loja não sabe o perfil de cada uma). */
  const alvo = plano.notas.filter(
    (nota) => !nota.jaEmitida && (!somente.length || somente.includes(nota.chave) || (nota.modelo === "nfse" && somente.includes("nfse")))
  );

  const pendencias = [...plano.pendenciasGerais, ...alvo.flatMap((nota) => nota.pendencias)].filter(
    (p) => !(input.previa && p.codigo === "creditos.sem_saldo")
  );
  if (!alvo.length) {
    return { notas: [], bloqueado: true, pendencias: [...pendencias, { codigo: "pedido.nada", nivel: "aviso", onde: "pedido", titulo: "Não há nota para emitir" }] };
  }
  if (temBloqueio(pendencias) || !plano.emissorGuardado) {
    return { notas: [], bloqueado: true, pendencias };
  }

  if (!input.previa && input.ambiente === "producao" && alvo.some((nota) => nota.ambienteNota !== "producao")) {
    const { usadas, limite } = await limiteDeTeste(admin, input.empresaId, input.ambiente);
    if (usadas + alvo.length > limite) {
      return {
        notas: [],
        bloqueado: true,
        pendencias: [
          {
            codigo: "creditos.limite_teste",
            nivel: "bloqueio",
            onde: "empresa",
            titulo: "Limite de notas de teste do mês",
            detalhe: `Este mês já saíram ${usadas} notas de teste (limite ${limite}). Ligue a emissão de verdade para seguir emitindo.`,
          },
        ],
      };
    }
  }

  const emissor = plano.emissorGuardado;
  const resultado: ResultadoDaEmissao = { notas: [], bloqueado: false, pendencias };

  for (const [indice, planejada] of alvo.entries()) {
    const nota = await criarOuReaproveitarNota(admin, {
      empresaId: input.empresaId,
      ambiente: input.ambiente,
      emissor,
      planejada,
      pedidoId: input.pedidoId,
      origem: plano.origem,
      destinatario: plano.destinatario,
      usuarioId: input.usuarioId,
      idNovo: indice === 0 ? input.idDaNovaNota ?? null : null,
      formulario: input.formulario,
    });
    const enviada = await enviarNota(admin, nota.id, { emitir: !input.previa, ator: input.usuarioId });
    resultado.notas.push({ id: enviada.id, modelo: enviada.modelo, status: enviada.status, mensagem: enviada.mensagem });
  }
  return resultado;
}

async function criarOuReaproveitarNota(
  admin: Admin,
  input: {
    empresaId: string;
    ambiente: AmbienteFiscal;
    emissor: EmissorGuardado;
    planejada: NotaPlanejada;
    pedidoId: string | null;
    origem: OrigemDaNota;
    destinatario: DestinatarioDaNota | null;
    usuarioId: string | null;
    idNovo?: string | null;
    formulario?: Record<string, unknown> | null;
  }
): Promise<NotaGuardada> {
  const { planejada } = input;
  if (!planejada.montada) throw new ErroFiscal("Nota sem dados para montar.", 400);

  let id = planejada.existente?.id ?? null;

  /* Trocou de modelo (NFC-e rejeitada, agora NF-e): a antiga sai de cena. */
  if (id && planejada.existente && planejada.existente.modelo !== planejada.modelo) {
    await descartarPrevia(admin, { empresaId: input.empresaId, notaId: id, usuarioId: input.usuarioId });
    id = null;
  }

  if (!id) {
    const novo = input.idNovo && UUID.test(input.idNovo) ? input.idNovo : crypto.randomUUID();
    const { data, error } = await admin
      .from("fiscal_notas")
      .insert({
        id: novo,
        empresa_id: input.empresaId,
        ambiente: input.ambiente,
        emissor_id: input.emissor.id,
        modelo: planejada.modelo,
        ambiente_nota: planejada.ambienteNota,
        origem: input.origem,
        pedido_id: input.pedidoId,
        grupo: planejada.grupo,
        status: "preparando",
        valor: planejada.montada.valor / 100,
        conta_credito: planejada.contaCredito,
        emitida_por: input.usuarioId,
      })
      .select("id")
      .maybeSingle();
    if (error) {
      /* Dois cliques juntos: a trava do banco deixou um passar. Usa o dele. */
      if (error.code === "23505" && !input.pedidoId && input.idNovo) {
        /* Dois cliques na nota sem pedido: a primeira já está a caminho. */
        const { data: mesma } = await admin.from("fiscal_notas").select("*").eq("id", novo).maybeSingle();
        if (!mesma) throw new ErroFiscal("Outra emissão desta nota está em andamento. Aguarde um instante.", 409);
        return mesma as NotaGuardada;
      } else if (error.code === "23505" && input.pedidoId) {
        const { data: outra } = await admin
          .from("fiscal_notas")
          .select("id")
          .eq("pedido_id", input.pedidoId)
          .eq("modelo", planejada.modelo)
          .eq("ambiente", input.ambiente)
          .eq("grupo", planejada.grupo)
          .in("status", ATIVAS_NO_PEDIDO)
          .maybeSingle();
        if (!outra) throw new ErroFiscal("Outra emissão deste pedido está em andamento. Aguarde um instante.", 409);
        id = String(outra.id);
      } else {
        throw new ErroFiscal(`Falha ao registrar a nota: ${error.message}`, 500);
      }
    } else {
      id = String(data?.id ?? novo);
    }
  }

  /* O payload é remontado com o id definitivo (integrationId). */
  const payload = { ...planejada.montada.payload, integrationId: id };
  const destinatario = input.destinatario
    ? { nome: input.destinatario.nome, documento: input.destinatario.documento, email: input.destinatario.email }
    : null;
  const { data: atualizada, error: erroAtualizar } = await admin
    .from("fiscal_notas")
    .update({
      payload,
      valor: planejada.montada.valor / 100,
      destinatario,
      ambiente_nota: planejada.ambienteNota,
      conta_credito: planejada.contaCredito,
      emissor_id: input.emissor.id,
      ...(input.formulario !== undefined ? { formulario: input.formulario } : {}),
      updated_at: new Date().toISOString(),
    })
    .eq("id", id)
    .in("status", ["preparando", "rascunho", "rejeitada", "erro"])
    .select("*")
    .maybeSingle();
  if (erroAtualizar) throw new ErroFiscal(`Falha ao preparar a nota: ${erroAtualizar.message}`, 500);
  if (!atualizada) {
    const { data: atual } = await admin.from("fiscal_notas").select("*").eq("id", id).maybeSingle();
    if (!atual) throw new ErroFiscal("Nota não encontrada.", 404);
    return atual as NotaGuardada;
  }
  return atualizada as NotaGuardada;
}

async function carregarNota(admin: Admin, notaId: string) {
  const { data, error } = await admin.from("fiscal_notas").select("*").eq("id", notaId).maybeSingle();
  if (error) throw new ErroFiscal(`Falha ao ler a nota: ${error.message}`, 500);
  if (!data) throw new ErroFiscal("Nota não encontrada.", 404);
  return data as NotaGuardada;
}

async function emissorDaNota(admin: Admin, nota: NotaGuardada) {
  const { data, error } = await admin.from("fiscal_emissores").select("*").eq("id", nota.emissor_id).maybeSingle();
  if (error || !data) throw new ErroFiscal("A empresa emissora desta nota não foi encontrada.", 404);
  return data as EmissorGuardado;
}

/** Muda o status só se ninguém mudou antes (webhook e consulta chegam juntos). */
async function atualizarNota(
  admin: Admin,
  nota: NotaGuardada,
  mudancas: Partial<NotaGuardada>
): Promise<NotaGuardada | null> {
  const { data, error } = await admin
    .from("fiscal_notas")
    .update({ ...mudancas, updated_at: new Date().toISOString() })
    .eq("id", nota.id)
    .eq("status", nota.status)
    .select("*")
    .maybeSingle();
  if (error) throw new ErroFiscal(`Falha ao atualizar a nota: ${error.message}`, 500);
  return (data as NotaGuardada | null) ?? null;
}

export async function enviarNota(
  admin: Admin,
  notaId: string,
  opcoes: { emitir: boolean; ator: string | null }
): Promise<NotaGuardada> {
  let nota = await carregarNota(admin, notaId);
  if (!["preparando", "rascunho", "rejeitada", "erro", "enviando"].includes(nota.status)) return nota;
  if (!nota.payload) throw new ErroFiscal("A nota não tem dados para enviar.", 400);
  const emissor = await emissorDaNota(admin, nota);

  /* Suspensão é decisão do owner e vale para todo envio novo (reenvio, prévia, teste).
     A nota que já estava a caminho ("enviando") termina: ela pode já existir na Spedy. */
  if (opcoes.emitir && emissor.status === "suspenso" && nota.status !== "enviando") {
    throw new ErroFiscal("A emissão desta empresa está suspensa. Fale com o suporte da Ktalogu.", 403);
  }

  /* Assinatura do Ktalogu vencida: nada novo sai, nem pela fila, nem de teste.
     A rota já barra quem está logado; isto segura o que chega sem ninguém
     logado (a reserva da venda do PDV, o reenvio agendado). */
  if (opcoes.emitir && nota.status !== "enviando") {
    const assinatura = await assinaturaDaEmpresa(admin, nota.empresa_id);
    if (!assinatura.ativa) {
      const mudou = await atualizarNota(admin, nota, { status: "erro", mensagem: assinatura.mensagem, mensagem_codigo: "ASSINATURA_FECHADA" });
      await registrarEvento(admin, {
        notaId: nota.id,
        empresaId: nota.empresa_id,
        tipo: "assinatura_fechada",
        titulo: "Não enviada: assinatura do Ktalogu vencida",
        detalhe: assinatura.mensagem,
        ator: opcoes.ator,
      });
      return mudou ?? nota;
    }
  }

  if (opcoes.emitir && nota.conta_credito) {
    const { data: reserva, error } = await admin.rpc("fiscal_reservar_credito", { p_nota: nota.id });
    if (error) throw new ErroFiscal(`Falha ao reservar a nota do pacote: ${error.message}`, 500);
    if (objeto(reserva).resultado === "sem_saldo") {
      const mudou = await atualizarNota(admin, nota, {
        status: "erro",
        mensagem: "Sem notas disponíveis no pacote deste mês. Compre um pacote em Assinatura → Notas fiscais.",
        mensagem_codigo: "SEM_SALDO",
      });
      await registrarEvento(admin, {
        notaId: nota.id,
        empresaId: nota.empresa_id,
        tipo: "sem_saldo",
        titulo: "Não enviada: pacote do mês sem notas",
        ator: opcoes.ator,
      });
      return mudou ?? nota;
    }
    nota = await carregarNota(admin, nota.id);
  }

  const payload = { ...nota.payload, integrationId: nota.id, issue: opcoes.emitir };
  const enviando = opcoes.emitir ? await atualizarNota(admin, nota, { status: "enviando", payload, mensagem: null, mensagem_codigo: null }) : nota;
  if (!enviando) return carregarNota(admin, nota.id);
  nota = enviando;

  try {
    const resposta = await criarNota(spedyDoEmissor(emissor), nota.modelo, payload);
    await registrarEvento(admin, {
      notaId: nota.id,
      empresaId: nota.empresa_id,
      tipo: opcoes.emitir ? "enviada" : "previa",
      titulo: opcoes.emitir ? `${NOME_DO_MODELO[nota.modelo]} enviada para autorização` : "Prévia gerada",
      ator: opcoes.ator,
    });
    const aplicada = await aplicarNotaDaSpedy(admin, nota, resposta, { rascunho: !opcoes.emitir });
    if (opcoes.emitir) {
      await agendarTarefa(admin, { ambiente: nota.ambiente, tipo: "consultar", empresaId: nota.empresa_id, notaId: nota.id, emSegundos: 12, dados: { rodada: 0 } });
    }
    return aplicada;
  } catch (erro) {
    if (erro instanceof ErroDaSpedy && erro.transitorio && opcoes.emitir) {
      await admin.from("fiscal_notas").update({ mensagem: erro.message, updated_at: new Date().toISOString() }).eq("id", nota.id);
      await agendarTarefa(admin, { ambiente: nota.ambiente, tipo: "emitir", empresaId: nota.empresa_id, notaId: nota.id, emSegundos: 60 });
      await registrarEvento(admin, {
        notaId: nota.id,
        empresaId: nota.empresa_id,
        tipo: "reenvio_agendado",
        titulo: "Serviço de notas instável: nova tentativa em 1 minuto",
        detalhe: erro.message,
        ator: opcoes.ator,
      });
      return carregarNota(admin, nota.id);
    }
    const mensagem = erro instanceof Error ? erro.message : "Falha ao enviar a nota.";
    const atual = await carregarNota(admin, nota.id);

    if (!opcoes.emitir) {
      /* A prévia não tem efeito fiscal: se ela não sai, a nota em preparo some e
         a tela recebe o motivo, em vez de uma "nota com problema" na lista. */
      if (atual.status === "preparando") {
        await admin
          .from("fiscal_notas")
          .update({ status: "removida", mensagem, updated_at: new Date().toISOString() })
          .eq("id", nota.id)
          .eq("status", "preparando");
      }
      await registrarEvento(admin, {
        notaId: nota.id,
        empresaId: nota.empresa_id,
        tipo: "previa_falhou",
        titulo: "A prévia não foi gerada",
        detalhe: mensagem,
        ator: opcoes.ator,
      });
      if (erro instanceof ErroDaSpedy && (erro.transitorio || erro.status >= 500)) {
        throw new ErroFiscal("Não foi possível gerar a prévia agora. Tente de novo em instantes, ou emita direto: a nota só vale depois de autorizada.", 503);
      }
      const explicacao = explicarRejeicao(erro instanceof ErroDaSpedy ? String(erro.status) : null, mensagem);
      throw new ErroFiscal(`A prévia não foi gerada: ${explicacao.titulo}. ${explicacao.dica}`, 422);
    }

    await admin
      .from("fiscal_notas")
      .update({
        status: opcoes.emitir || atual.status === "preparando" ? "erro" : atual.status,
        mensagem,
        mensagem_codigo: erro instanceof ErroDaSpedy ? String(erro.status) : null,
        resposta: erro instanceof ErroDaSpedy ? (erro.corpo as object) : null,
        updated_at: new Date().toISOString(),
      })
      .eq("id", nota.id);
    await admin.rpc("fiscal_liberar_credito", { p_nota: nota.id });
    await registrarEvento(admin, {
      notaId: nota.id,
      empresaId: nota.empresa_id,
      tipo: "erro_envio",
      titulo: "Os dados da nota não foram aceitos",
      detalhe: mensagem,
      ator: opcoes.ator,
    });
    return carregarNota(admin, nota.id);
  }
}

/* ================================================================== */
/* Aplicar o que a Spedy diz                                           */
/* ================================================================== */

export async function aplicarNotaDaSpedy(
  admin: Admin,
  notaInicial: NotaGuardada,
  spedy: SpedyNota,
  opcoes: { rascunho?: boolean } = {}
): Promise<NotaGuardada> {
  for (let tentativa = 0; tentativa < 3; tentativa += 1) {
    const nota = tentativa === 0 ? notaInicial : await carregarNota(admin, notaInicial.id);
    const rascunho = opcoes.rascunho === true || (nota.status === "rascunho" && spedy.status === "created");
    let novo = statusDaSpedyParaLocal(spedy.status, rascunho);
    const detalhe = spedy.processingDetail ?? null;

    /* Cancelamento pedido e a Spedy ainda diz "autorizada": espera, a menos que ela
       diga que o cancelamento falhou. */
    let mensagemDeCancelamento: string | null = null;
    if (nota.status === "cancelando" && novo === "autorizada") {
      if (detalhe?.status === "failed") {
        mensagemDeCancelamento = detalhe.message ? `Cancelamento recusado: ${detalhe.message}` : "Cancelamento recusado.";
      } else {
        novo = "cancelando";
      }
    }

    const mudancas: Partial<NotaGuardada> = {
      spedy_status: String(spedy.status || ""),
      spedy_nota_id: spedy.id || nota.spedy_nota_id,
      resposta: spedy,
    };
    if (spedy.number !== null && spedy.number !== undefined) mudancas.numero = Number(spedy.number);
    if (spedy.series) mudancas.serie = String(spedy.series);
    if (spedy.accessKey) mudancas.chave = String(spedy.accessKey);
    if (spedy.authorization?.protocol) mudancas.protocolo = String(spedy.authorization.protocol);
    const autorizadaEm = dataDaSpedy(spedy.authorization?.date);
    const canceladaEm = dataDaSpedy(spedy.cancellation?.date);
    if (autorizadaEm) mudancas.autorizada_em = autorizadaEm;
    if (canceladaEm) mudancas.cancelada_em = canceladaEm;
    if (spedy.cancellation?.protocol) mudancas.cancelamento_protocolo = String(spedy.cancellation.protocol);
    if (novo === "autorizada" && !mudancas.autorizada_em && !nota.autorizada_em) mudancas.autorizada_em = new Date().toISOString();
    if (novo === "cancelada" && !mudancas.cancelada_em && !nota.cancelada_em) mudancas.cancelada_em = new Date().toISOString();

    if (["rejeitada", "denegada", "erro"].includes(novo)) {
      mudancas.mensagem = detalhe?.message ?? nota.mensagem ?? "A autoridade recusou a nota.";
      mudancas.mensagem_codigo = detalhe?.code ?? null;
    } else if (mensagemDeCancelamento) {
      mudancas.mensagem = mensagemDeCancelamento;
      mudancas.mensagem_codigo = detalhe?.code ?? null;
    } else if (["autorizada", "cancelada", "processando", "contingencia", "rascunho"].includes(novo)) {
      mudancas.mensagem = novo === "processando" && detalhe?.message ? detalhe.message : null;
      mudancas.mensagem_codigo = novo === "processando" ? detalhe?.code ?? null : null;
    }

    const mudaStatus = novo !== nota.status;
    if (mudaStatus && !podeMudarStatus(nota.status, novo)) {
      /* Aviso atrasado: guarda o que veio, sem voltar o status. */
      await admin.from("fiscal_notas").update({ spedy_status: mudancas.spedy_status, spedy_nota_id: mudancas.spedy_nota_id }).eq("id", nota.id);
      return carregarNota(admin, nota.id);
    }
    if (mudaStatus) mudancas.status = novo;

    const salva = await atualizarNota(admin, nota, mudancas);
    if (!salva) continue;

    if (mudaStatus) await efeitosDaMudanca(admin, nota, salva);
    return salva;
  }
  return carregarNota(admin, notaInicial.id);
}

async function efeitosDaMudanca(admin: Admin, antes: NotaGuardada, depois: NotaGuardada) {
  const aparencia = APARENCIA_DO_STATUS[depois.status];
  const modelo = NOME_DO_MODELO[depois.modelo];
  const numero = depois.numero ? ` nº ${depois.numero}` : "";

  if (depois.status === "autorizada") {
    await admin.rpc("fiscal_consumir_credito", { p_nota: depois.id });
    await agendarTarefa(admin, { ambiente: depois.ambiente, tipo: "baixar_arquivos", empresaId: depois.empresa_id, notaId: depois.id, emSegundos: 3 });
    await registrarEvento(admin, {
      notaId: depois.id,
      empresaId: depois.empresa_id,
      tipo: "autorizada",
      titulo: antes.status === "cancelando" ? `Cancelamento não aceito: ${modelo}${numero} continua autorizada` : `${modelo}${numero} autorizada`,
      detalhe: depois.mensagem ?? (depois.protocolo ? `Protocolo ${depois.protocolo}` : null),
    });
    return;
  }
  if (depois.status === "cancelada") {
    await admin.rpc("fiscal_liberar_credito", { p_nota: depois.id });
    await agendarTarefa(admin, { ambiente: depois.ambiente, tipo: "baixar_arquivos", empresaId: depois.empresa_id, notaId: depois.id, emSegundos: 5 });
    await registrarEvento(admin, {
      notaId: depois.id,
      empresaId: depois.empresa_id,
      tipo: "cancelada",
      titulo: `${modelo}${numero} cancelada`,
      detalhe: depois.cancelamento_motivo,
    });
    return;
  }
  if (["rejeitada", "denegada", "removida", "erro"].includes(depois.status)) {
    await admin.rpc("fiscal_liberar_credito", { p_nota: depois.id });
    const explicacao = explicarRejeicao(depois.mensagem_codigo, depois.mensagem, { teste: depois.origem === "teste", modelo: depois.modelo });
    await registrarEvento(admin, {
      notaId: depois.id,
      empresaId: depois.empresa_id,
      tipo: depois.status,
      titulo: `${modelo} ${aparencia.rotulo.toLowerCase()}${depois.mensagem_codigo ? ` (${depois.mensagem_codigo})` : ""}`,
      detalhe: [depois.mensagem, explicacao.dica].filter(Boolean).join(" — "),
    });
    return;
  }
  if (depois.status === "contingencia") {
    await registrarEvento(admin, {
      notaId: depois.id,
      empresaId: depois.empresa_id,
      tipo: "contingencia",
      titulo: `${modelo}${numero} emitida em contingência`,
      detalhe: "A SEFAZ estava fora do ar. A nota será confirmada automaticamente quando ela voltar.",
    });
  }
}

/* ================================================================== */
/* Consultar, baixar arquivos, cancelar, corrigir                      */
/* ================================================================== */

const ESPERAS_DA_CONSULTA = [15, 30, 60, 120, 300, 600, 900, 1800];

export async function consultarNota(admin: Admin, notaId: string, rodada = 0): Promise<NotaGuardada> {
  const nota = await carregarNota(admin, notaId);
  const emissor = await emissorDaNota(admin, nota);
  const ctx = spedyDoEmissor(emissor);

  let spedy: SpedyNota | null = null;
  if (nota.spedy_nota_id) {
    spedy = await obterNota(ctx, nota.modelo, nota.spedy_nota_id);
  } else {
    spedy = await procurarNotaPorIntegracao(ctx, nota.modelo, nota.id);
    if (!spedy) {
      const minutos = (Date.now() - new Date(nota.updated_at).getTime()) / 60000;
      if (nota.status === "enviando" && minutos > 30) {
        const mudou = await atualizarNota(admin, nota, {
          status: "erro",
          mensagem: "O recebimento desta nota não foi confirmado. Revise e envie de novo.",
          mensagem_codigo: "SEM_CONFIRMACAO",
        });
        if (mudou) await efeitosDaMudanca(admin, nota, mudou);
        return mudou ?? carregarNota(admin, nota.id);
      }
      if (nota.status === "enviando") {
        await agendarTarefa(admin, { ambiente: nota.ambiente, tipo: "consultar", empresaId: nota.empresa_id, notaId: nota.id, emSegundos: 60, dados: { rodada: rodada + 1 } });
      }
      return nota;
    }
  }

  let aplicada = await aplicarNotaDaSpedy(admin, nota, spedy);

  if (["enviando", "processando", "cancelando", "contingencia"].includes(aplicada.status)) {
    /* Depois de muitas voltas sem resposta, pede para a Spedy conferir direto na autoridade. */
    if (rodada === 5 && aplicada.status !== "contingencia" && aplicada.spedy_nota_id) {
      try {
        const conferida = await consultarNaAutoridade(ctx, aplicada.modelo, aplicada.spedy_nota_id);
        if (conferida?.status) aplicada = await aplicarNotaDaSpedy(admin, aplicada, conferida);
      } catch {
        /* 400 enquanto está na fila da Spedy: segue esperando. */
      }
    }
    const espera = ESPERAS_DA_CONSULTA[Math.min(rodada, ESPERAS_DA_CONSULTA.length - 1)];
    const horas = (Date.now() - new Date(aplicada.created_at).getTime()) / 3600000;
    if (horas < 72) {
      await agendarTarefa(admin, {
        ambiente: aplicada.ambiente,
        tipo: "consultar",
        empresaId: aplicada.empresa_id,
        notaId: aplicada.id,
        emSegundos: espera,
        dados: { rodada: rodada + 1 },
      });
    }
  }
  return aplicada;
}

/**
 * Espera a resposta da SEFAZ perguntando direto ao serviço de notas, sem fila:
 * é o que o balcão usa para o cupom sair na hora (a NFC-e costuma ser
 * autorizada em poucos segundos). Para no primeiro status final ou no limite;
 * a consulta agendada pela fila continua valendo depois.
 */
export async function aguardarResposta(
  admin: Admin,
  notaId: string,
  opcoes: { ateMs?: number; intervaloMs?: number } = {}
): Promise<NotaGuardada> {
  const limite = Date.now() + (opcoes.ateMs ?? 12_000);
  const intervalo = opcoes.intervaloMs ?? 800;
  let nota = await carregarNota(admin, notaId);
  if (!["enviando", "processando"].includes(nota.status) || !nota.spedy_nota_id) return nota;
  const emissor = await emissorDaNota(admin, nota);
  const ctx = spedyDoEmissor(emissor);

  while (Date.now() + intervalo < limite) {
    await new Promise((resolver) => setTimeout(resolver, intervalo));
    try {
      const spedy = await obterNota(ctx, nota.modelo, nota.spedy_nota_id as string, { timeoutMs: 5_000, tentativas: 1 });
      nota = await aplicarNotaDaSpedy(admin, nota, spedy);
    } catch {
      /* Uma consulta que falha não derruba a espera: tenta na próxima volta. */
      nota = await carregarNota(admin, notaId);
    }
    if (!["enviando", "processando"].includes(nota.status)) break;
  }
  return nota;
}

export function caminhoDoArquivo(nota: Pick<NotaGuardada, "empresa_id" | "ambiente" | "modelo" | "id" | "created_at">, tipo: "pdf" | "xml") {
  const data = new Date(nota.created_at);
  const ano = data.getUTCFullYear();
  const mes = String(data.getUTCMonth() + 1).padStart(2, "0");
  return `${nota.empresa_id}/${nota.ambiente}/${nota.modelo}/${ano}/${mes}/${nota.id}.${tipo}`;
}

export async function baixarArquivos(admin: Admin, notaId: string): Promise<NotaGuardada> {
  const nota = await carregarNota(admin, notaId);
  if (!["autorizada", "cancelada"].includes(nota.status) || !nota.spedy_nota_id) return nota;
  const emissor = await emissorDaNota(admin, nota);
  const ctx = spedyDoEmissor(emissor);

  const [pdf, xml] = await Promise.all([
    baixarArquivoDaNota(ctx, nota.modelo, nota.spedy_nota_id, "pdf"),
    baixarArquivoDaNota(ctx, nota.modelo, nota.spedy_nota_id, "xml"),
  ]);
  const pdfPath = caminhoDoArquivo(nota, "pdf");
  const xmlPath = caminhoDoArquivo(nota, "xml");
  const armazem = admin.storage.from(BUCKET_FISCAL);
  const [envioPdf, envioXml] = await Promise.all([
    armazem.upload(pdfPath, new Uint8Array(pdf), { contentType: "application/pdf", upsert: true }),
    armazem.upload(xmlPath, new Uint8Array(xml), { contentType: "application/xml", upsert: true }),
  ]);
  if (envioPdf.error || envioXml.error) {
    throw new Error(`Falha ao guardar os arquivos: ${envioPdf.error?.message || envioXml.error?.message}`);
  }
  await admin
    .from("fiscal_notas")
    .update({ pdf_path: pdfPath, xml_path: xmlPath, arquivos_em: new Date().toISOString() })
    .eq("id", nota.id);
  return carregarNota(admin, nota.id);
}

export async function arquivoDaNota(
  admin: Admin,
  input: { empresaId: string; notaId: string; tipo: "pdf" | "xml"; previa?: boolean }
): Promise<{ conteudo: ArrayBuffer; nome: string; tipoMime: string }> {
  const nota = await carregarNota(admin, input.notaId);
  if (nota.empresa_id !== input.empresaId) throw new ErroFiscal("Nota não encontrada.", 404);
  const nome = `${NOME_DO_MODELO[nota.modelo]}${nota.numero ? `-${nota.numero}` : ""}${nota.chave ? `-${nota.chave}` : ""}.${input.tipo}`;
  const tipoMime = input.tipo === "pdf" ? "application/pdf" : "application/xml";

  const caminho = input.tipo === "pdf" ? nota.pdf_path : nota.xml_path;
  if (caminho && !input.previa && nota.status !== "cancelada") {
    const { data } = await admin.storage.from(BUCKET_FISCAL).download(caminho);
    if (data) return { conteudo: await data.arrayBuffer(), nome, tipoMime };
  }

  if (!nota.spedy_nota_id) throw new ErroFiscal("Esta nota ainda não tem arquivo.", 404);
  const podeBaixar = input.previa ? nota.status === "rascunho" : ["autorizada", "cancelada", "contingencia"].includes(nota.status);
  if (!podeBaixar) throw new ErroFiscal("O arquivo fica disponível depois da autorização.", 409);
  const emissor = await emissorDaNota(admin, nota);
  const conteudo = await baixarArquivoDaNota(spedyDoEmissor(emissor), nota.modelo, nota.spedy_nota_id, input.tipo, input.previa === true);
  return { conteudo, nome: input.previa ? `previa-${nome}` : nome, tipoMime };
}

export async function cancelarNotaFiscal(
  admin: Admin,
  input: { empresaId: string; notaId: string; motivo: string; usuarioId: string | null }
): Promise<NotaGuardada> {
  const motivo = input.motivo.replace(/\s+/g, " ").trim();
  if (motivo.length < 15) throw new ErroFiscal("Escreva o motivo do cancelamento com pelo menos 15 letras.", 400);
  if (motivo.length > 255) throw new ErroFiscal("O motivo do cancelamento pode ter até 255 letras.", 400);

  const nota = await carregarNota(admin, input.notaId);
  if (nota.empresa_id !== input.empresaId) throw new ErroFiscal("Nota não encontrada.", 404);
  if (nota.status === "cancelada" || nota.status === "cancelando") return nota;
  if (nota.status === "contingencia") {
    throw new ErroFiscal("Nota em contingência só pode ser cancelada depois que a SEFAZ confirmar a autorização.", 409);
  }
  if (nota.status !== "autorizada" || !nota.spedy_nota_id) throw new ErroFiscal("Só nota autorizada pode ser cancelada.", 409);

  const emissor = await emissorDaNota(admin, nota);
  const marcada = await atualizarNota(admin, nota, {
    status: "cancelando",
    cancelamento_motivo: motivo,
    cancelada_por: input.usuarioId,
    mensagem: null,
    mensagem_codigo: null,
  });
  if (!marcada) return carregarNota(admin, nota.id);

  try {
    const resposta = await cancelarNota(spedyDoEmissor(emissor), nota.modelo, nota.spedy_nota_id, motivo);
    await registrarEvento(admin, {
      notaId: nota.id,
      empresaId: nota.empresa_id,
      tipo: "cancelamento_pedido",
      titulo: "Cancelamento enviado",
      detalhe: motivo,
      ator: input.usuarioId,
    });
    const aplicada = resposta?.status ? await aplicarNotaDaSpedy(admin, marcada, resposta) : marcada;
    if (aplicada.status === "cancelando") {
      await agendarTarefa(admin, { ambiente: nota.ambiente, tipo: "consultar", empresaId: nota.empresa_id, notaId: nota.id, emSegundos: 10, dados: { rodada: 0 } });
    }
    return aplicada;
  } catch (erro) {
    const mensagem = erro instanceof Error ? erro.message : "O cancelamento não foi aceito.";
    await admin
      .from("fiscal_notas")
      .update({ status: "autorizada", mensagem: `Cancelamento recusado: ${mensagem}`, updated_at: new Date().toISOString() })
      .eq("id", nota.id)
      .eq("status", "cancelando");
    await registrarEvento(admin, {
      notaId: nota.id,
      empresaId: nota.empresa_id,
      tipo: "cancelamento_recusado",
      titulo: "Cancelamento recusado",
      detalhe: mensagem,
      ator: input.usuarioId,
    });
    throw new ErroFiscal(mensagem, erro instanceof ErroDaSpedy && erro.transitorio ? 503 : 422);
  }
}

export async function cartaDeCorrecaoFiscal(
  admin: Admin,
  input: { empresaId: string; notaId: string; texto: string; usuarioId: string | null }
) {
  const correcao = input.texto.replace(/\s+/g, " ").trim();
  if (correcao.length < 15) throw new ErroFiscal("A correção precisa ter pelo menos 15 letras.", 400);
  if (correcao.length > 1000) throw new ErroFiscal("A correção pode ter até 1000 letras.", 400);
  const nota = await carregarNota(admin, input.notaId);
  if (nota.empresa_id !== input.empresaId) throw new ErroFiscal("Nota não encontrada.", 404);
  if (nota.modelo !== "nfe") throw new ErroFiscal("Carta de correção só existe para NF-e.", 409);
  if (nota.status !== "autorizada" || !nota.spedy_nota_id) throw new ErroFiscal("Só NF-e autorizada recebe carta de correção.", 409);
  const emissor = await emissorDaNota(admin, nota);
  const resposta = await cartaDeCorrecao(spedyDoEmissor(emissor), nota.spedy_nota_id, correcao);
  await registrarEvento(admin, {
    notaId: nota.id,
    empresaId: nota.empresa_id,
    tipo: "correcao",
    titulo: "Carta de correção enviada",
    detalhe: correcao,
    dados: resposta,
    ator: input.usuarioId,
  });
  await agendarTarefa(admin, { ambiente: nota.ambiente, tipo: "baixar_arquivos", empresaId: nota.empresa_id, notaId: nota.id, emSegundos: 30 });
  return resposta;
}

export async function reenviarEmailFiscal(admin: Admin, input: { empresaId: string; notaId: string; usuarioId: string | null }) {
  const nota = await carregarNota(admin, input.notaId);
  if (nota.empresa_id !== input.empresaId) throw new ErroFiscal("Nota não encontrada.", 404);
  if (!["autorizada", "cancelada"].includes(nota.status) || !nota.spedy_nota_id) {
    throw new ErroFiscal("O e-mail sai depois da autorização.", 409);
  }
  const email = texto(objeto(nota.destinatario).email);
  if (!email) throw new ErroFiscal("O cliente desta nota não tem e-mail cadastrado.", 409);
  const emissor = await emissorDaNota(admin, nota);
  await reenviarEmail(spedyDoEmissor(emissor), nota.modelo, nota.spedy_nota_id);
  await registrarEvento(admin, {
    notaId: nota.id,
    empresaId: nota.empresa_id,
    tipo: "email",
    titulo: `E-mail reenviado para ${email}`,
    ator: input.usuarioId,
  });
}

export async function descartarPrevia(admin: Admin, input: { empresaId: string; notaId: string; usuarioId: string | null }) {
  const nota = await carregarNota(admin, input.notaId);
  if (nota.empresa_id !== input.empresaId) throw new ErroFiscal("Nota não encontrada.", 404);
  if (!["rascunho", "erro", "rejeitada", "preparando"].includes(nota.status)) {
    throw new ErroFiscal("Só prévia ou nota não autorizada pode ser descartada.", 409);
  }
  if (nota.spedy_nota_id && nota.status === "rascunho") {
    try {
      const emissor = await emissorDaNota(admin, nota);
      await cancelarNota(spedyDoEmissor(emissor), nota.modelo, nota.spedy_nota_id, "Previa descartada no Ktalogu");
    } catch {
      /* Prévia não tem efeito fiscal: se a Spedy não apagar, some só daqui. */
    }
  }
  const mudou = await atualizarNota(admin, nota, { status: "removida" });
  if (mudou) {
    await admin.rpc("fiscal_liberar_credito", { p_nota: nota.id });
    await registrarEvento(admin, { notaId: nota.id, empresaId: nota.empresa_id, tipo: "descartada", titulo: "Nota descartada", ator: input.usuarioId });
  }
  return mudou ?? carregarNota(admin, nota.id);
}

export async function reenviarNotaDoPedido(
  admin: Admin,
  input: { empresaId: string; ambiente: AmbienteFiscal; notaId: string; usuarioId: string | null; escolhas?: EscolhasDaEmissao }
) {
  const nota = await carregarNota(admin, input.notaId);
  if (nota.empresa_id !== input.empresaId) throw new ErroFiscal("Nota não encontrada.", 404);
  if (!["rejeitada", "erro", "rascunho"].includes(nota.status)) throw new ErroFiscal("Esta nota não precisa ser reenviada.", 409);
  if (!nota.pedido_id) return enviarNota(admin, nota.id, { emitir: true, ator: input.usuarioId });
  const chave = nota.modelo === "nfse" ? `nfse:${nota.grupo}` : nota.modelo;
  const resultado = await emitirDoPedido(admin, {
    empresaId: input.empresaId,
    ambiente: input.ambiente,
    pedidoId: nota.pedido_id,
    usuarioId: input.usuarioId,
    escolhas: { ...(input.escolhas ?? {}), modeloDeMercadoria: nota.modelo === "nfse" ? input.escolhas?.modeloDeMercadoria : nota.modelo, somente: [chave] },
  });
  if (resultado.bloqueado) throw new ErroFiscal("Ainda há pendências para reenviar esta nota.", 422, { pendencias: resultado.pendencias });
  return carregarNota(admin, nota.id);
}

/** A consulta manual ("Atualizar status"). */
export async function atualizarStatusAgora(admin: Admin, input: { empresaId: string; notaId: string }) {
  const nota = await carregarNota(admin, input.notaId);
  if (nota.empresa_id !== input.empresaId) throw new ErroFiscal("Nota não encontrada.", 404);
  if (!nota.spedy_nota_id && nota.status !== "enviando") return nota;
  return consultarNota(admin, nota.id, 0);
}

export async function notaDaEmpresa(admin: Admin, empresaId: string, notaId: string) {
  const nota = await carregarNota(admin, notaId);
  if (nota.empresa_id !== empresaId) throw new ErroFiscal("Nota não encontrada.", 404);
  return nota;
}

export { carregarNota };
