import { pendenciasDoServidor } from "@/lib/fiscal/ambiente";
import type { Presenca } from "@/lib/fiscal/emissor";
import { notaParaTela, planoParaTela } from "@/lib/fiscal/servidor/apresentacao";
import { servidorPronto, type Admin } from "@/lib/fiscal/servidor/base";
import { acordarFila } from "@/lib/fiscal/servidor/fila";
import { emitirDoPedido, ErroFiscal, planejarEmissao, type EscolhasDaEmissao, type NotaGuardada } from "@/lib/fiscal/servidor/notas";
import { ncmExiste } from "@/lib/fiscal/servidor/consultas";
import { exigirUuid, lerCorpo, rotaFiscal } from "@/lib/fiscal/servidor/rota";
import { LIMITE_DAS_OBSERVACOES } from "@/lib/orders/observacoes";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

type Contexto = { params: Promise<{ id: string }> };

const PRESENCAS: Presenca[] = ["presence", "internet", "telephone", "delivery", "othersNonPresenceOperation"];

function lerEscolhas(bruto: unknown): EscolhasDaEmissao {
  const r = bruto && typeof bruto === "object" ? (bruto as Record<string, unknown>) : {};
  return {
    modeloDeMercadoria: r.modeloDeMercadoria === "nfce" ? "nfce" : r.modeloDeMercadoria === "nfe" ? "nfe" : null,
    consumidorFinal: typeof r.consumidorFinal === "boolean" ? r.consumidorFinal : null,
    presenca: PRESENCAS.includes(r.presenca as Presenca) ? (r.presenca as Presenca) : null,
    /* Texto (mesmo vazio) = o que a pessoa deixou no campo; nulo = não mexeu, vale a do pedido. */
    observacao: typeof r.observacao === "string" ? r.observacao.slice(0, LIMITE_DAS_OBSERVACOES) : null,
    enviarEmail: typeof r.enviarEmail === "boolean" ? r.enviarEmail : null,
    documento: typeof r.documento === "string" ? r.documento : null,
    somente: Array.isArray(r.somente) ? r.somente.filter((item): item is string => typeof item === "string").slice(0, 10) : null,
  };
}

async function notasDoPedido(admin: Admin, empresaId: string, ambiente: string, pedidoId: string) {
  const { data } = await admin
    .from("fiscal_notas")
    .select("*")
    .eq("empresa_id", empresaId)
    .eq("ambiente", ambiente)
    .eq("pedido_id", pedidoId)
    .order("created_at", { ascending: false });
  return ((data ?? []) as NotaGuardada[]).map((nota) => notaParaTela(nota));
}

/** O que a gaveta "Nota fiscal" do pedido mostra: notas que já saíram e o plano do que falta. */
export async function GET(req: Request, { params }: Contexto) {
  const { id } = await params;
  return rotaFiscal("fiscal.ver", async ({ admin, empresaId, ambiente }) => {
    const pedidoId = exigirUuid(id, "Pedido");
    const busca = new URL(req.url).searchParams;
    const notas = await notasDoPedido(admin, empresaId, ambiente, pedidoId);
    if (busca.get("so") === "notas") return { ambiente, notas };
    const escolhas = lerEscolhas({
      modeloDeMercadoria: busca.get("modelo"),
      consumidorFinal: busca.get("consumidorFinal") === null ? undefined : busca.get("consumidorFinal") === "1",
      presenca: busca.get("presenca"),
      documento: busca.get("documento"),
    });
    const plano = await planejarEmissao(admin, { empresaId, ambiente, pedidoId, escolhas });
    return { ambiente, servidorPronto: pendenciasDoServidor().length === 0, notas, plano: planoParaTela(plano) };
  });
}

/** Emite (ou gera a prévia de) as notas do pedido. */
export async function POST(req: Request, { params }: Contexto) {
  const { id } = await params;
  return rotaFiscal("fiscal.emitir", async ({ admin, empresaId, ambiente, user }) => {
    const bloqueio = servidorPronto();
    if (bloqueio) throw new ErroFiscal(bloqueio, 503);
    const pedidoId = exigirUuid(id, "Pedido");
    const corpo = await lerCorpo(req);
    const resultado = await emitirDoPedido(admin, {
      empresaId,
      ambiente,
      pedidoId,
      usuarioId: user.id,
      escolhas: lerEscolhas(corpo.escolhas),
      previa: corpo.previa === true,
    });
    acordarFila();
    if (resultado.bloqueado) {
      return Response.json({ error: "Há pendências para emitir.", pendencias: resultado.pendencias }, { status: 422 });
    }
    return { resultado, notas: await notasDoPedido(admin, empresaId, ambiente, pedidoId) };
  });
}

/*
 * O NCM de um item digitado à mão no pedido (sem produto do catálogo). Vale
 * só para a nota deste pedido; vazio apaga. Não mexe em estoque nem em valor.
 */
export async function PATCH(req: Request, { params }: Contexto) {
  const { id } = await params;
  return rotaFiscal("fiscal.emitir", async ({ admin, empresaId }) => {
    const pedidoId = exigirUuid(id, "Pedido");
    const corpo = await lerCorpo(req);
    const itemId = exigirUuid(typeof corpo.itemId === "string" ? corpo.itemId : "", "Item");
    const ncm = (typeof corpo.ncm === "string" ? corpo.ncm : "").replace(/\D/g, "");
    if (ncm && ncm.length !== 8) throw new ErroFiscal("O NCM tem 8 números (ex.: 3926.40.00).", 400);
    if (ncm && (await ncmExiste(ncm)) === false) {
      throw new ErroFiscal("Esse NCM não existe na tabela oficial. Confira o número com o seu contador.", 400);
    }
    const { data: ordem } = await admin.from("orders").select("id").eq("id", pedidoId).eq("user_id", empresaId).maybeSingle();
    if (!ordem) throw new ErroFiscal("Pedido não encontrado.", 404);
    const { data, error } = await admin.from("order_items").update({ fiscal_ncm: ncm || null }).eq("id", itemId).eq("order_id", pedidoId).select("id");
    if (error) throw new ErroFiscal("Não foi possível guardar o NCM. Tente de novo.", 500);
    if (!data?.length) throw new ErroFiscal("Este item não é deste pedido.", 404);
    return { ok: true };
  });
}
