/**
 * Inutilizar numeração: avisar a SEFAZ que uma faixa de números nunca virou
 * nota e nunca vai virar.
 *
 * Acontece quando um número "queima" — a nota falhou no meio do caminho, o
 * contador seguiu em frente e ficou um buraco na sequência. A SEFAZ cobra a
 * explicação do buraco, e quem não faz leva multa e não fecha a escrituração.
 *
 * Duas travas moram aqui: número que virou nota autorizada nunca pode ser
 * inutilizado, e a faixa é conferida contra o que já saiu deste sistema antes
 * de ir para a SEFAZ — errar aqui é irreversível.
 */

import type { AmbienteFiscal } from "@/lib/fiscal/ambiente";
import { lerNfce, lerNfe } from "@/lib/fiscal/emissor";
import { inutilizarNumeracao, obterInutilizacao, xmlDaInutilizacao, type SpedyInutilizacao } from "@/lib/spedy/api";

import { carregarEmissor, spedyDoEmissor, type Admin } from "./base";
import { ErroFiscal } from "./notas";

export type ModeloComNumeracao = "nfe" | "nfce";

export type InutilizacaoGuardada = {
  id: string;
  empresa_id: string;
  ambiente: AmbienteFiscal;
  modelo: ModeloComNumeracao;
  serie: string;
  numero_inicial: number;
  numero_final: number;
  motivo: string;
  status: "processando" | "registrada" | "rejeitada" | "erro";
  spedy_id: string | null;
  protocolo: string | null;
  mensagem: string | null;
  mensagem_codigo: string | null;
  created_at: string;
  updated_at: string;
};

const STATUS_DA_SPEDY: Record<string, InutilizacaoGuardada["status"]> = {
  inProcessing: "processando",
  registered: "registrada",
  rejected: "rejeitada",
  error: "erro",
};

function comoFicou(resposta: SpedyInutilizacao | null | undefined) {
  return {
    status: STATUS_DA_SPEDY[String(resposta?.status ?? "")] ?? "processando",
    protocolo: resposta?.protocolNumber ?? null,
    mensagem: resposta?.messageText ?? null,
    mensagem_codigo: resposta?.messageCode ?? null,
    spedy_id: resposta?.id ?? null,
  };
}

/** A série que a empresa usa em cada tipo de nota, para a tela já vir preenchida. */
export async function seriesDaEmpresa(admin: Admin, empresaId: string, ambiente: AmbienteFiscal) {
  const emissor = await carregarEmissor(admin, empresaId, ambiente);
  return {
    nfe: lerNfe(emissor?.nfe).serie || "1",
    nfce: lerNfce(emissor?.nfce).serie || "1",
    configurada: Boolean(emissor?.spedy_empresa_id && emissor?.chave_cifrada),
  };
}

export async function listarInutilizacoesGuardadas(admin: Admin, empresaId: string, ambiente: AmbienteFiscal) {
  const { data, error } = await admin
    .from("fiscal_inutilizacoes")
    .select("*")
    .eq("empresa_id", empresaId)
    .eq("ambiente", ambiente)
    .order("created_at", { ascending: false })
    .limit(50);
  if (error) throw new ErroFiscal(`Falha ao ler as inutilizações: ${error.message}`, 500);
  return (data ?? []) as InutilizacaoGuardada[];
}

/** Números da faixa que já viraram nota — a SEFAZ recusaria, e com razão. */
export async function numerosOcupados(
  admin: Admin,
  input: { empresaId: string; ambiente: AmbienteFiscal; modelo: ModeloComNumeracao; serie: string; de: number; ate: number }
) {
  const { data } = await admin
    .from("fiscal_notas")
    .select("numero,status,serie")
    .eq("empresa_id", input.empresaId)
    .eq("ambiente", input.ambiente)
    .eq("modelo", input.modelo)
    .in("status", ["autorizada", "cancelada", "processando", "enviando", "contingencia"])
    .gte("numero", input.de)
    .lte("numero", input.ate)
    .limit(200);
  return (data ?? [])
    .filter((nota) => String(nota.serie ?? "") === String(input.serie))
    .map((nota) => Number(nota.numero))
    .filter((numero) => Number.isFinite(numero))
    .sort((a, b) => a - b);
}

export async function inutilizarFaixa(
  admin: Admin,
  input: {
    empresaId: string;
    ambiente: AmbienteFiscal;
    modelo: ModeloComNumeracao;
    serie: string;
    de: number;
    ate: number;
    motivo: string;
    usuarioId: string | null;
  }
): Promise<InutilizacaoGuardada> {
  const de = Math.trunc(Number(input.de));
  const ate = Math.trunc(Number(input.ate));
  const serie = String(input.serie ?? "").trim();
  const motivo = String(input.motivo ?? "").replace(/\s+/g, " ").trim();

  if (!Number.isFinite(de) || !Number.isFinite(ate) || de < 1 || ate < de) {
    throw new ErroFiscal("Informe o primeiro e o último número da faixa, do menor para o maior.", 400);
  }
  if (ate - de > 999) throw new ErroFiscal("A faixa pode ter até 1.000 números de cada vez.", 400);
  if (!/^\d{1,3}$/.test(serie)) throw new ErroFiscal("A série é um número de até 3 dígitos, como 1.", 400);
  if (motivo.length < 15) throw new ErroFiscal("Escreva o motivo com pelo menos 15 letras. Ele vai para a SEFAZ.", 400);
  if (motivo.length > 255) throw new ErroFiscal("O motivo pode ter até 255 letras.", 400);

  const emissor = await carregarEmissor(admin, input.empresaId, input.ambiente);
  if (!emissor?.spedy_empresa_id || !emissor.chave_cifrada) {
    throw new ErroFiscal("Configure a empresa antes de inutilizar numeração.", 409);
  }

  const ocupados = await numerosOcupados(admin, { ...input, de, ate, serie });
  if (ocupados.length) {
    const lista = ocupados.slice(0, 8).join(", ");
    throw new ErroFiscal(
      `Nesta faixa ${ocupados.length === 1 ? "o número" : "os números"} ${lista} já ${ocupados.length === 1 ? "virou nota" : "viraram nota"}. Inutilize só os números que nunca foram usados.`,
      409
    );
  }

  const { data: registro, error } = await admin
    .from("fiscal_inutilizacoes")
    .insert({
      empresa_id: input.empresaId,
      ambiente: input.ambiente,
      emissor_id: emissor.id,
      modelo: input.modelo,
      serie,
      numero_inicial: de,
      numero_final: ate,
      motivo,
      criada_por: input.usuarioId,
    })
    .select("*")
    .single();
  if (error || !registro) throw new ErroFiscal(`Falha ao registrar: ${error?.message ?? ""}`, 500);

  try {
    const resposta = await inutilizarNumeracao(spedyDoEmissor(emissor), input.modelo, {
      series: Number(serie),
      initialNumber: de,
      finalNumber: ate,
      reason: motivo,
    });
    const { data: atualizada } = await admin
      .from("fiscal_inutilizacoes")
      .update({ ...comoFicou(resposta), updated_at: new Date().toISOString() })
      .eq("id", registro.id)
      .select("*")
      .single();
    return (atualizada ?? registro) as InutilizacaoGuardada;
  } catch (erro) {
    const mensagem = erro instanceof Error ? erro.message : "Falha ao falar com o serviço de notas.";
    await admin
      .from("fiscal_inutilizacoes")
      .update({ status: "erro", mensagem: mensagem.slice(0, 500), updated_at: new Date().toISOString() })
      .eq("id", registro.id);
    throw erro;
  }
}

export async function conferirInutilizacao(admin: Admin, input: { empresaId: string; ambiente: AmbienteFiscal; id: string }) {
  const { data } = await admin.from("fiscal_inutilizacoes").select("*").eq("id", input.id).maybeSingle();
  const registro = data as InutilizacaoGuardada | null;
  if (!registro || registro.empresa_id !== input.empresaId) throw new ErroFiscal("Inutilização não encontrada.", 404);
  if (!registro.spedy_id || registro.status === "registrada") return registro;

  const emissor = await carregarEmissor(admin, input.empresaId, input.ambiente);
  if (!emissor?.chave_cifrada) return registro;
  const resposta = await obterInutilizacao(spedyDoEmissor(emissor), registro.modelo, registro.spedy_id);
  const { data: atualizada } = await admin
    .from("fiscal_inutilizacoes")
    .update({ ...comoFicou(resposta), updated_at: new Date().toISOString() })
    .eq("id", registro.id)
    .select("*")
    .single();
  return (atualizada ?? registro) as InutilizacaoGuardada;
}

export async function xmlDaInutilizacaoGuardada(
  admin: Admin,
  input: { empresaId: string; ambiente: AmbienteFiscal; id: string }
): Promise<{ conteudo: ArrayBuffer; nome: string }> {
  const registro = await conferirInutilizacao(admin, input);
  if (registro.status !== "registrada" || !registro.spedy_id) {
    throw new ErroFiscal("O arquivo fica disponível depois que a SEFAZ registra a inutilização.", 409);
  }
  const emissor = await carregarEmissor(admin, input.empresaId, input.ambiente);
  if (!emissor?.chave_cifrada) throw new ErroFiscal("Configure a empresa primeiro.", 409);
  const conteudo = await xmlDaInutilizacao(spedyDoEmissor(emissor), registro.modelo, registro.spedy_id);
  return { conteudo, nome: `inutilizacao-${registro.serie}-${registro.numero_inicial}-${registro.numero_final}.xml` };
}
