/**
 * O pacote do contador: os XMLs (e, se pedir, os PDFs) de um período em um
 * arquivo só.
 *
 * A regra do que entra é a do fisco, não a da tela: nota autorizada e nota
 * cancelada entram (o cancelamento também é escriturado); rascunho, recusada e
 * removida ficam de fora, porque não existem para a contabilidade. As notas
 * recebidas entram em uma pasta à parte, e só as completas — o resumo da SEFAZ
 * não vale para escrituração.
 */

import type { AmbienteFiscal } from "@/lib/fiscal/ambiente";

import { baixarArquivoDaNota, baixarArquivoDaRecebida } from "@/lib/spedy/api";

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

export type PedidoDeExportacao = {
  empresaId: string;
  ambiente: AmbienteFiscal;
  de: string;
  ate: string;
  incluirPdf?: boolean;
  incluirRecebidas?: boolean;
};

const MODELO_EM_PASTA: Record<string, string> = { nfe: "nota-de-produto", nfce: "cupom-fiscal", nfse: "nota-de-servico" };

function competenciaLegivel(de: string, ate: string) {
  return de.slice(0, 7) === ate.slice(0, 7) ? de.slice(0, 7) : `${de}_a_${ate}`;
}

function diaValido(valor: string, padrao: string) {
  return /^\d{4}-\d{2}-\d{2}$/.test(valor) ? valor : padrao;
}

/** O intervalo pedido, já defendido: no máximo 12 meses e sempre de/até certos. */
export function periodoDaExportacao(de: string | null | undefined, ate: string | null | undefined) {
  const hoje = new Date();
  const primeiroDoMes = new Date(Date.UTC(hoje.getUTCFullYear(), hoje.getUTCMonth(), 1)).toISOString().slice(0, 10);
  const ultimoDia = new Date(Date.UTC(hoje.getUTCFullYear(), hoje.getUTCMonth() + 1, 0)).toISOString().slice(0, 10);
  let inicio = diaValido(String(de ?? ""), primeiroDoMes);
  let fim = diaValido(String(ate ?? ""), ultimoDia);
  if (inicio > fim) [inicio, fim] = [fim, inicio];
  const limite = new Date(`${inicio}T00:00:00Z`);
  limite.setUTCMonth(limite.getUTCMonth() + 12);
  if (new Date(`${fim}T00:00:00Z`) > limite) fim = limite.toISOString().slice(0, 10);
  return { de: inicio, ate: fim };
}

export async function contarNotasDoPeriodo(admin: Admin, pedido: PedidoDeExportacao) {
  const { de, ate } = periodoDaExportacao(pedido.de, pedido.ate);
  const { count } = await admin
    .from("fiscal_notas")
    .select("id", { count: "exact", head: true })
    .eq("empresa_id", pedido.empresaId)
    .eq("ambiente", pedido.ambiente)
    .in("status", ["autorizada", "cancelada"])
    .gte("created_at", `${de}T00:00:00Z`)
    .lte("created_at", `${ate}T23:59:59Z`);
  return count ?? 0;
}

export async function montarPacoteDoContador(
  admin: Admin,
  pedido: PedidoDeExportacao
): Promise<{ conteudo: ArrayBuffer; nome: string; notas: number; faltaram: number }> {
  const { de, ate } = periodoDaExportacao(pedido.de, pedido.ate);
  const { data, error } = await admin
    .from("fiscal_notas")
    .select("id,modelo,numero,serie,chave,status,created_at,xml_path,pdf_path,spedy_nota_id,emissor_id")
    .eq("empresa_id", pedido.empresaId)
    .eq("ambiente", pedido.ambiente)
    .in("status", ["autorizada", "cancelada"])
    .gte("created_at", `${de}T00:00:00Z`)
    .lte("created_at", `${ate}T23:59:59Z`)
    .order("created_at", { ascending: true })
    .limit(3000);
  if (error) throw new ErroFiscal(`Falha ao listar as notas: ${error.message}`, 500);

  const notas = data ?? [];
  const emissor = await carregarEmissor(admin, pedido.empresaId, pedido.ambiente);
  const ctx = emissor?.chave_cifrada ? spedyDoEmissor(emissor) : null;
  const armazem = admin.storage.from(BUCKET_FISCAL);
  const arquivos: ArquivoDoZip[] = [];
  let faltaram = 0;

  const juntar = async (caminho: string | null, baixar: () => Promise<ArrayBuffer>, nome: string, quando: Date) => {
    try {
      if (caminho) {
        const { data: guardado } = await armazem.download(caminho);
        if (guardado) {
          arquivos.push({ nome, conteudo: new Uint8Array(await guardado.arrayBuffer()), quando });
          return true;
        }
      }
      const baixado = await baixar();
      arquivos.push({ nome, conteudo: new Uint8Array(baixado), quando });
      return true;
    } catch {
      faltaram += 1;
      return false;
    }
  };

  for (const nota of notas) {
    const quando = new Date(nota.created_at as string);
    const pasta = MODELO_EM_PASTA[String(nota.modelo)] ?? String(nota.modelo);
    const identificacao = [nota.numero ? `${nota.numero}` : null, nota.chave ? String(nota.chave) : null].filter(Boolean).join("-") || String(nota.id);
    const base = `${pasta}/${nota.status === "cancelada" ? "canceladas/" : ""}${identificacao}`;
    await juntar(
      nota.xml_path as string | null,
      async () => {
        if (!ctx || !nota.spedy_nota_id) throw new Error("sem origem");
        return baixarArquivoDaNota(ctx, nota.modelo as "nfe" | "nfce" | "nfse", String(nota.spedy_nota_id), "xml");
      },
      `${base}.xml`,
      quando
    );
    if (pedido.incluirPdf) {
      await juntar(
        nota.pdf_path as string | null,
        async () => {
          if (!ctx || !nota.spedy_nota_id) throw new Error("sem origem");
          return baixarArquivoDaNota(ctx, nota.modelo as "nfe" | "nfce" | "nfse", String(nota.spedy_nota_id), "pdf");
        },
        `${base}.pdf`,
        quando
      );
    }
  }

  if (pedido.incluirRecebidas) {
    const { data: recebidas } = await admin
      .from("fiscal_notas_recebidas")
      .select("id,modelo,chave,emitida_em,created_at,xml_path,spedy_id,completa")
      .eq("empresa_id", pedido.empresaId)
      .eq("ambiente", pedido.ambiente)
      .eq("completa", true)
      .gte("emitida_em", `${de}T00:00:00Z`)
      .lte("emitida_em", `${ate}T23:59:59Z`)
      .limit(2000);
    for (const nota of recebidas ?? []) {
      const quando = new Date((nota.emitida_em as string) ?? (nota.created_at as string));
      const nome = `recebidas/${nota.chave ? String(nota.chave) : String(nota.id)}.xml`;
      await juntar(
        nota.xml_path as string | null,
        async () => {
          if (!ctx) throw new Error("sem origem");
          return baixarArquivoDaRecebida(ctx, nota.modelo as "nfe" | "nfse", String(nota.spedy_id), "xml");
        },
        nome,
        quando
      );
    }
  }

  if (!arquivos.length) {
    throw new ErroFiscal("Não há nota autorizada nesse período para juntar no pacote.", 404);
  }

  const pacote = montarZip(arquivos);
  return {
    conteudo: pacote.buffer.slice(pacote.byteOffset, pacote.byteOffset + pacote.byteLength) as ArrayBuffer,
    nome: `notas-fiscais-${competenciaLegivel(de, ate)}.zip`,
    notas: notas.length,
    faltaram,
  };
}
