import { createAdminClient } from "@/lib/supabase-admin";

/**
 * As notas fiscais vistas pelo dono da Ktalogu.
 *
 * O plano da Spedy é um só para a plataforma inteira (notas por mês, CNPJs
 * emissores, preço da nota excedente). Aqui ele é cruzado com o que as
 * empresas compraram e emitiram, para o owner ver margem, vagas e a saúde da
 * integração num lugar só.
 *
 * O banco é o mesmo para o servidor local e o de produção: tudo aqui é lido e
 * gravado por ambiente (sandbox ou producao), igual ao admin.
 */

export type AmbienteFiscal = "sandbox" | "producao";

type Admin = ReturnType<typeof createAdminClient>;

export class ErroDoPainel extends Error {
  status: number;
  constructor(mensagem: string, status = 400) {
    super(mensagem);
    this.status = status;
  }
}

export function ambientePadrao(): AmbienteFiscal {
  const declarado = String(process.env.SPEDY_AMBIENTE || "").trim().toLowerCase();
  if (declarado === "producao" || declarado === "produção" || declarado === "production") return "producao";
  if (declarado === "sandbox") return "sandbox";
  return process.env.NODE_ENV === "production" ? "producao" : "sandbox";
}

export function lerAmbiente(valor: unknown): AmbienteFiscal {
  if (valor === "producao" || valor === "sandbox") return valor;
  return ambientePadrao();
}

export type RegrasFiscais = {
  limite_emissores: number;
  notas_teste_por_mes: number;
  aviso_saldo_baixo: number;
  plano_spedy_notas: number;
  plano_spedy_valor: number;
  plano_spedy_nota_excedente: number;
  vendas_abertas: boolean;
  updated_at: string | null;
  updated_by: string | null;
};

export type PacoteFiscal = {
  id: string;
  nome: string;
  descricao: string | null;
  quantidade: number;
  preco: number;
  ativo: boolean;
  destaque: boolean;
  ordem: number;
};

const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

export function exigirUuid(valor: unknown, oQue = "Registro") {
  const texto = typeof valor === "string" ? valor.trim() : "";
  if (!UUID.test(texto)) throw new ErroDoPainel(`${oQue} inválido.`, 400);
  return texto;
}

/** Mês corrente em São Paulo, no formato da coluna `competencia` (AAAA-MM-01). */
export function competenciaAtual(agora = new Date()) {
  const anoEMes = new Intl.DateTimeFormat("en-CA", { timeZone: "America/Sao_Paulo", year: "numeric", month: "2-digit" }).format(agora);
  return `${anoEMes}-01`;
}

/** Meia-noite do dia 1 em São Paulo (sem horário de verão desde 2019). */
function inicioDaCompetencia(competencia: string) {
  return new Date(`${competencia}T00:00:00-03:00`).toISOString();
}

/** O PostgREST do Supabase devolve no máximo 1.000 linhas por consulta. */
async function todasAsLinhas<T>(
  pagina: (de: number, ate: number) => PromiseLike<{ data: unknown; error: { message: string } | null }>
): Promise<T[]> {
  const saida: T[] = [];
  const tamanho = 1000;
  for (let de = 0; de < 500_000; de += tamanho) {
    const { data, error } = await pagina(de, de + tamanho - 1);
    if (error) throw new Error(error.message);
    const linhas = Array.isArray(data) ? (data as T[]) : [];
    saida.push(...linhas);
    if (linhas.length < tamanho) break;
  }
  return saida;
}

const numero = (valor: unknown, padrao: number) => {
  const n = Number(valor);
  return valor !== null && valor !== undefined && Number.isFinite(n) ? n : padrao;
};

function lerRegras(bruto: unknown): RegrasFiscais {
  const r = bruto && typeof bruto === "object" ? (bruto as Record<string, unknown>) : {};
  return {
    limite_emissores: numero(r.limite_emissores, 30),
    notas_teste_por_mes: numero(r.notas_teste_por_mes, 30),
    aviso_saldo_baixo: numero(r.aviso_saldo_baixo, 10),
    plano_spedy_notas: numero(r.plano_spedy_notas, 1000),
    plano_spedy_valor: numero(r.plano_spedy_valor, 129),
    plano_spedy_nota_excedente: numero(r.plano_spedy_nota_excedente, 0.161),
    vendas_abertas: r.vendas_abertas !== false,
    updated_at: typeof r.updated_at === "string" ? r.updated_at : null,
    updated_by: typeof r.updated_by === "string" ? r.updated_by : null,
  };
}

const objeto = (valor: unknown): Record<string, unknown> =>
  valor && typeof valor === "object" && !Array.isArray(valor) ? (valor as Record<string, unknown>) : {};

type LinhaDoEmissor = {
  id: string;
  empresa_id: string;
  status: string;
  cnpj: string;
  razao_social: string;
  nome_fantasia: string | null;
  regime: string;
  nfe: unknown;
  nfce: unknown;
  nfse: unknown;
  certificado: unknown;
  certificado_validade: string | null;
  spedy_empresa_id: string | null;
  chave_cifrada: string | null;
  ultimo_erro: string | null;
  endereco: unknown;
  created_at: string;
};

const CAMPOS_DO_EMISSOR =
  "id,empresa_id,status,cnpj,razao_social,nome_fantasia,regime,nfe,nfce,nfse,certificado,certificado_validade,spedy_empresa_id,chave_cifrada,ultimo_erro,endereco,created_at";

function diasParaVencer(validade: string | null | undefined) {
  if (!validade) return null;
  const fim = new Date(validade).getTime();
  if (!Number.isFinite(fim)) return null;
  return Math.floor((fim - Date.now()) / (24 * 60 * 60 * 1000));
}

export async function painelFiscal(ambiente: AmbienteFiscal) {
  const admin = createAdminClient();
  const competencia = competenciaAtual();
  const inicio = inicioDaCompetencia(competencia);
  const ha24h = new Date(Date.now() - 24 * 3600 * 1000).toISOString();

  const meiaHora = new Date(Date.now() - 30 * 60 * 1000).toISOString();
  const [
    regrasR,
    pacotesR,
    emissoresR,
    comprasR,
    filaR,
    avisosR,
    errosR,
    rejeitadasR,
    notasDoMes,
    creditosDoMes,
    reservasDoMes,
    aprovadasDoMes,
    travadasR,
  ] = await Promise.all([
      admin.from("fiscal_regras").select("*").eq("id", 1).maybeSingle(),
      admin.from("fiscal_pacotes").select("id,nome,descricao,quantidade,preco,ativo,destaque,ordem").order("ordem").order("quantidade"),
      admin.from("fiscal_emissores").select(CAMPOS_DO_EMISSOR).eq("ambiente", ambiente).order("created_at", { ascending: false }).limit(1000),
      admin
        .from("fiscal_compras")
        .select("id,empresa_id,pacote_nome,quantidade,preco,status,forma_pagamento,mp_status_detalhe,aprovada_em,created_at")
        .eq("ambiente", ambiente)
        .order("created_at", { ascending: false })
        .limit(60),
      admin
        .from("fiscal_fila")
        .select("id,tipo,executar_em,tentativas,travada_em", { count: "exact" })
        .eq("ambiente", ambiente)
        .is("concluida_em", null)
        .order("executar_em")
        .limit(200),
      admin.from("fiscal_webhooks").select("origem,recebido_em,processado_em,erro").order("recebido_em", { ascending: false }).limit(100),
      admin
        .from("fiscal_fila")
        .select("id,tipo,erro,tentativas,concluida_em,created_at")
        .eq("ambiente", ambiente)
        .not("erro", "is", null)
        .gte("created_at", ha24h)
        .order("created_at", { ascending: false })
        .limit(12),
      admin
        .from("fiscal_notas")
        .select("id", { count: "exact", head: true })
        .eq("ambiente", ambiente)
        .in("status", ["rejeitada", "denegada", "erro"])
        .gte("created_at", inicio),
      todasAsLinhas<{ empresa_id: string; ambiente_nota: string }>((de, ate) =>
        admin
          .from("fiscal_notas")
          .select("id,empresa_id,ambiente_nota")
          .eq("ambiente", ambiente)
          .in("status", ["autorizada", "cancelada", "cancelando"])
          .gte("autorizada_em", inicio)
          .order("id")
          .range(de, ate)
      ),
      todasAsLinhas<{ empresa_id: string; tipo: string; quantidade: number }>((de, ate) =>
        admin
          .from("fiscal_creditos")
          .select("id,empresa_id,tipo,quantidade")
          .eq("ambiente", ambiente)
          .eq("competencia", competencia)
          .order("id")
          .range(de, ate)
      ),
      todasAsLinhas<{ empresa_id: string }>((de, ate) =>
        admin
          .from("fiscal_notas")
          .select("id,empresa_id")
          .eq("ambiente", ambiente)
          .eq("credito", "reservado")
          .eq("credito_competencia", competencia)
          .order("id")
          .range(de, ate)
      ),
      todasAsLinhas<{ preco: number }>((de, ate) =>
        admin
          .from("fiscal_compras")
          .select("id,preco")
          .eq("ambiente", ambiente)
          .eq("status", "aprovada")
          .eq("competencia", competencia)
          .order("id")
          .range(de, ate)
      ),
      /* Nota que saiu e não voltou: meia hora parada já é tempo demais. */
      admin
        .from("fiscal_notas")
        .select("id,empresa_id,modelo,status,updated_at")
        .eq("ambiente", ambiente)
        .in("status", ["enviando", "processando", "cancelando", "preparando"])
        .lt("updated_at", meiaHora)
        .order("updated_at", { ascending: true })
        .limit(50),
    ]);

  for (const resultado of [regrasR, pacotesR, emissoresR, comprasR, filaR, avisosR, errosR]) {
    if (resultado.error) throw new Error(resultado.error.message);
  }

  const regras = lerRegras(regrasR.data);
  const emissores = (emissoresR.data ?? []) as LinhaDoEmissor[];
  const compras = (comprasR.data ?? []) as Array<Record<string, unknown>>;

  /* Nomes das empresas: a loja (profiles) e, na falta, o cadastro de acesso. */
  const empresasIds = [...new Set([...emissores.map((e) => e.empresa_id), ...compras.map((c) => String(c.empresa_id))])];
  const nomes = new Map<string, { nome: string; email: string | null }>();
  for (let i = 0; i < empresasIds.length; i += 100) {
    const lote = empresasIds.slice(i, i + 100);
    const [perfis, acessos] = await Promise.all([
      admin.from("profiles").select("id,store_name,slug").in("id", lote),
      admin.from("users_app").select("id,name,email").in("id", lote),
    ]);
    const listaDePerfis = (perfis.data ?? []) as Array<{ id: string; store_name: string | null; slug: string | null }>;
    const listaDeAcessos = (acessos.data ?? []) as Array<{ id: string; name: string | null; email: string | null }>;
    for (const id of lote) {
      const perfil = listaDePerfis.find((p) => p.id === id);
      const acesso = listaDeAcessos.find((a) => a.id === id);
      nomes.set(id, {
        nome: perfil?.store_name?.trim() || acesso?.name?.trim() || perfil?.slug || acesso?.email || "Empresa sem nome",
        email: acesso?.email ?? null,
      });
    }
  }

  /* Uso do mês por empresa, na mesma conta do `fiscal_saldo` do banco. */
  type Uso = { entradas: number; usadas: number; reservadas: number; comValor: number; emTeste: number };
  const uso = new Map<string, Uso>();
  const usoDe = (empresaId: string) => {
    let atual = uso.get(empresaId);
    if (!atual) {
      atual = { entradas: 0, usadas: 0, reservadas: 0, comValor: 0, emTeste: 0 };
      uso.set(empresaId, atual);
    }
    return atual;
  };
  for (const credito of creditosDoMes) {
    const atual = usoDe(credito.empresa_id);
    if (credito.tipo === "consumo") atual.usadas += -Number(credito.quantidade);
    else atual.entradas += Number(credito.quantidade);
  }
  for (const reserva of reservasDoMes) usoDe(reserva.empresa_id).reservadas += 1;
  let notasComValor = 0;
  let notasEmTeste = 0;
  for (const nota of notasDoMes) {
    if (nota.ambiente_nota === "producao") {
      notasComValor += 1;
      usoDe(nota.empresa_id).comValor += 1;
    } else {
      notasEmTeste += 1;
      usoDe(nota.empresa_id).emTeste += 1;
    }
  }

  const notasExcedentes = Math.max(0, notasComValor - regras.plano_spedy_notas);
  const custoSpedy = regras.plano_spedy_valor + notasExcedentes * regras.plano_spedy_nota_excedente;
  const receitaDePacotes = aprovadasDoMes.reduce((acc, compra) => acc + Number(compra.preco || 0), 0);

  const fila = (filaR.data ?? []) as Array<{ id: number; tipo: string; executar_em: string; tentativas: number; travada_em: string | null }>;
  const avisos = (avisosR.data ?? []) as Array<{ origem: string; recebido_em: string; processado_em: string | null; erro: string | null }>;
  const avisosNormais = new Set(["evento ignorado", "nota não encontrada"]);

  /* Pagamento contestado ou estornado depois que as notas saíram: o sistema
     não cobra de volta, então precisa aparecer aqui para alguém decidir. */
  const noventaDias = new Date(Date.now() - 90 * 24 * 3600 * 1000).toISOString();
  const { data: contestadasR } = await admin
    .from("fiscal_compras")
    .select("id,empresa_id,pacote_nome,preco,status,updated_at")
    .eq("ambiente", ambiente)
    .in("status", ["contestada", "reembolsada"])
    .gte("updated_at", noventaDias)
    .order("updated_at", { ascending: false })
    .limit(30);
  const contestacoes = ((contestadasR ?? []) as Array<{ id: string; empresa_id: string; pacote_nome: string; preco: number; status: string; updated_at: string }>).map(
    (c) => ({
      id: c.id,
      empresaId: c.empresa_id,
      empresa: nomes.get(c.empresa_id)?.nome ?? "Empresa",
      pacote: c.pacote_nome,
      preco: Number(c.preco),
      status: c.status as "contestada" | "reembolsada",
      quando: c.updated_at,
    })
  );

  /* Nota de serviço recusada: cada prefeitura tem o seu sistema e as suas
     exigências, e só a primeira nota de verdade de cada cidade mostra o que
     ela pede (o ambiente de teste da Spedy não fala com prefeitura nenhuma).
     A nota fica aqui, com a cidade, o sistema da prefeitura e a mensagem, até
     alguém corrigir e reenviar: reenviada, ela deixa de estar recusada. */
  const trintaDias = new Date(Date.now() - 30 * 24 * 3600 * 1000).toISOString();
  const { data: recusadasR } = await admin
    .from("fiscal_notas")
    .select("id,empresa_id,emissor_id,origem,valor,mensagem,mensagem_codigo,updated_at")
    .eq("ambiente", ambiente)
    .eq("modelo", "nfse")
    .eq("status", "rejeitada")
    .gte("updated_at", trintaDias)
    .order("updated_at", { ascending: false })
    .limit(30);
  const cidadeDaNotaDeServico = new Map(
    emissores.map((e) => {
      const cidade = objeto(objeto(e.nfse).cidade);
      const campo = (valor: unknown) => (typeof valor === "string" && valor.trim() ? valor.trim() : null);
      return [e.id, { nome: campo(cidade.nome), uf: campo(cidade.uf), provedor: campo(cidade.provedor) }] as const;
    })
  );
  const servicosRecusados = (
    (recusadasR ?? []) as Array<{
      id: string;
      empresa_id: string;
      emissor_id: string | null;
      origem: string | null;
      valor: number | string | null;
      mensagem: string | null;
      mensagem_codigo: string | null;
      updated_at: string;
    }>
  ).map((n) => {
    const cidade = (n.emissor_id && cidadeDaNotaDeServico.get(n.emissor_id)) || { nome: null, uf: null, provedor: null };
    return {
      id: n.id,
      empresaId: n.empresa_id,
      empresa: nomes.get(n.empresa_id)?.nome ?? "Empresa",
      cidade: cidade.nome ? `${cidade.nome}${cidade.uf ? `/${cidade.uf.toUpperCase()}` : ""}` : null,
      provedor: cidade.provedor,
      origem: n.origem,
      valor: Number(n.valor ?? 0),
      codigo: n.mensagem_codigo,
      mensagem: n.mensagem?.trim() || "Sem mensagem da prefeitura.",
      quando: n.updated_at,
    };
  });

  return {
    ambiente,
    ambientePadrao: ambientePadrao(),
    competencia,
    regras,
    pacotes: ((pacotesR.data ?? []) as PacoteFiscal[]).map((p) => ({ ...p, preco: Number(p.preco), quantidade: Number(p.quantidade) })),
    resumo: {
      notasComValor,
      notasEmTeste,
      notasExcedentes,
      custoSpedy,
      receitaDePacotes,
      comprasAprovadas: aprovadasDoMes.length,
      margem: receitaDePacotes - custoSpedy,
      notasVendidas: [...uso.values()].reduce((acc, u) => acc + Math.max(0, u.entradas), 0),
      vagasUsadas: emissores.filter((e) => e.spedy_empresa_id).length,
      rejeitadas: rejeitadasR.count ?? 0,
    },
    emissores: emissores.map((e) => {
      const deste = uso.get(e.empresa_id) ?? { entradas: 0, usadas: 0, reservadas: 0, comValor: 0, emTeste: 0 };
      const endereco = objeto(e.endereco);
      const certificado = objeto(e.certificado);
      const validade = (typeof certificado.validade === "string" ? certificado.validade : null) ?? e.certificado_validade;
      return {
        id: e.id,
        empresaId: e.empresa_id,
        empresaNome: nomes.get(e.empresa_id)?.nome ?? e.razao_social,
        email: nomes.get(e.empresa_id)?.email ?? null,
        razaoSocial: e.razao_social,
        nomeFantasia: e.nome_fantasia,
        cnpj: e.cnpj,
        status: e.status as "rascunho" | "ativo" | "suspenso",
        regime: e.regime,
        modelos: (["nfe", "nfce", "nfse"] as const)
          .filter((m) => objeto(e[m]).ativo === true)
          .map((m) => ({ modelo: m, producao: objeto(e[m]).ambiente === "producao" })),
        cidade:
          typeof endereco.cidade === "string" && endereco.cidade
            ? `${endereco.cidade}${endereco.uf ? `/${String(endereco.uf).toUpperCase()}` : ""}`
            : null,
        naSpedy: Boolean(e.spedy_empresa_id),
        certificadoDias: diasParaVencer(validade),
        ultimoErro: e.ultimo_erro,
        entradas: deste.entradas,
        usadas: deste.usadas,
        reservadas: deste.reservadas,
        disponiveis: deste.entradas - deste.usadas - deste.reservadas,
        notasComValor: deste.comValor,
        notasEmTeste: deste.emTeste,
        criadoEm: e.created_at,
      };
    }),
    compras: compras.map((c) => ({
      id: String(c.id),
      empresaId: String(c.empresa_id),
      empresaNome: nomes.get(String(c.empresa_id))?.nome ?? "Empresa",
      pacoteNome: String(c.pacote_nome),
      quantidade: Number(c.quantidade),
      preco: Number(c.preco),
      status: String(c.status),
      formaPagamento: typeof c.forma_pagamento === "string" ? c.forma_pagamento : null,
      detalhe: typeof c.mp_status_detalhe === "string" ? c.mp_status_detalhe : null,
      aprovadaEm: typeof c.aprovada_em === "string" ? c.aprovada_em : null,
      criadaEm: String(c.created_at),
    })),
    vigias: {
      contestacoes,
      servicosRecusados,
      /* Nota parada some da vista de quem emitiu e só reaparece quando o
         cliente cobra a nota. Aqui ela fica visível antes disso. */
      notasTravadas: ((travadasR.data ?? []) as Array<{ id: string; empresa_id: string; modelo: string; status: string; updated_at: string }>).map(
        (n) => ({
          id: n.id,
          empresaId: n.empresa_id,
          empresa: nomes.get(n.empresa_id)?.nome ?? null,
          modelo: n.modelo,
          status: n.status,
          desde: n.updated_at,
        })
      ),
      /* Certificado vencido é emissão parada no dia seguinte, sem aviso nenhum. */
      certificadosVencendo: emissores
        .map((e) => {
          const certificadoDele = objeto(e.certificado);
          const validade = (typeof certificadoDele.validade === "string" ? certificadoDele.validade : null) ?? e.certificado_validade;
          return {
            empresaId: e.empresa_id,
            empresa: nomes.get(e.empresa_id)?.nome ?? e.razao_social,
            dias: diasParaVencer(validade),
            validade,
          };
        })
        .filter((c) => c.dias !== null && c.dias <= 30)
        .sort((a, b) => (a.dias ?? 0) - (b.dias ?? 0))
        .slice(0, 20),
      /* As alíquotas do IBS/CBS são as do período de teste da reforma: quando o
         cronograma virar, é isto que precisa mudar no código. */
      reforma: {
        ibsUf: 0.1,
        ibsMunicipio: 0,
        cbs: 0.9,
        valeAte: "2026-12-31",
        situacao: (() => {
          const fim = new Date("2026-12-31T23:59:59Z").getTime();
          if (fim < Date.now()) return "vencidas" as const;
          return fim - Date.now() < 60 * 24 * 3600 * 1000 ? ("vence_em_breve" as const) : ("valendo" as const);
        })(),
      },
    },
    saude: {
      filaPendentes: filaR.count ?? fila.length,
      filaAtrasadas: fila.filter((t) => new Date(t.executar_em).getTime() < Date.now() - 10 * 60 * 1000).length,
      filaMaisAntiga: fila[0]?.executar_em ?? null,
      ultimoAvisoSpedy: avisos.find((a) => a.origem === "spedy")?.recebido_em ?? null,
      ultimoAvisoMercadoPago: avisos.find((a) => a.origem === "mercado_pago")?.recebido_em ?? null,
      avisosComFalha: avisos.filter((a) => a.erro && !avisosNormais.has(a.erro)).length,
      errosRecentes: (
        (errosR.data ?? []) as Array<{ id: number; tipo: string; erro: string | null; tentativas: number; concluida_em: string | null; created_at: string }>
      ).map((e) => ({ id: e.id, tipo: e.tipo, erro: e.erro ?? "", tentativas: e.tentativas, desistiu: Boolean(e.concluida_em), quando: e.created_at })),
    },
  };
}

export type PainelFiscal = Awaited<ReturnType<typeof painelFiscal>>;

/* ------------------------------------------------------------------ */
/* Gravações                                                           */
/* ------------------------------------------------------------------ */

const texto = (valor: unknown) => (typeof valor === "string" ? valor.trim() : "");

function decimalDoCampo(valor: unknown) {
  if (typeof valor === "number") return valor;
  const limpo = String(valor ?? "").trim().replace(/\s/g, "");
  if (!limpo) return Number.NaN;
  /* "1.234,56" e "1234.56" valem: com vírgula, ela é o decimal e o ponto é milhar. */
  const normalizado = limpo.includes(",") ? limpo.replace(/\./g, "").replace(",", ".") : limpo;
  return Number(normalizado);
}

export function lerPacote(bruto: unknown): Omit<PacoteFiscal, "id"> {
  const r = objeto(bruto);
  const nome = texto(r.nome).slice(0, 60);
  const quantidade = Number(r.quantidade);
  const preco = Math.round(decimalDoCampo(r.preco) * 100) / 100;
  if (nome.length < 2) throw new ErroDoPainel("Dê um nome ao pacote.");
  if (!Number.isInteger(quantidade) || quantidade < 1 || quantidade > 100000) {
    throw new ErroDoPainel("A quantidade de notas precisa ser um número inteiro entre 1 e 100.000.");
  }
  if (!Number.isFinite(preco) || preco < 1 || preco > 1000000) throw new ErroDoPainel("O preço precisa ser de pelo menos R$ 1,00.");
  const ordem = Number(r.ordem);
  return {
    nome,
    descricao: texto(r.descricao).slice(0, 160) || null,
    quantidade,
    preco,
    ativo: r.ativo !== false,
    destaque: r.destaque === true,
    ordem: Number.isInteger(ordem) ? Math.max(-1000, Math.min(1000, ordem)) : 0,
  };
}

/** Só um pacote é o "mais escolhido": marcar um desmarca os outros. */
async function destaqueUnico(admin: Admin, id: string) {
  const { error } = await admin.from("fiscal_pacotes").update({ destaque: false }).neq("id", id).eq("destaque", true);
  if (error) throw new Error(error.message);
}

export async function criarPacote(admin: Admin, bruto: unknown) {
  const pacote = lerPacote(bruto);
  const { data, error } = await admin.from("fiscal_pacotes").insert(pacote).select("id").single();
  if (error) throw new Error(error.message);
  const id = String((data as { id: string }).id);
  if (pacote.destaque) await destaqueUnico(admin, id);
  return id;
}

export async function alterarPacote(admin: Admin, id: string, bruto: unknown) {
  const pacote = lerPacote(bruto);
  const { data, error } = await admin
    .from("fiscal_pacotes")
    .update({ ...pacote, updated_at: new Date().toISOString() })
    .eq("id", id)
    .select("id");
  if (error) throw new Error(error.message);
  if (!data?.length) throw new ErroDoPainel("Pacote não encontrado.", 404);
  if (pacote.destaque) await destaqueUnico(admin, id);
}

/** Pacote já vendido não some do histórico: a compra guarda nome, notas e preço. */
export async function excluirPacote(admin: Admin, id: string) {
  const { data, error } = await admin.from("fiscal_pacotes").delete().eq("id", id).select("id");
  if (error) throw new Error(error.message);
  if (!data?.length) throw new ErroDoPainel("Pacote não encontrado.", 404);
}

export async function salvarRegras(admin: Admin, bruto: unknown, email: string | null) {
  const r = objeto(bruto);
  const inteiro = (valor: unknown, rotulo: string, maximo: number) => {
    const n = Number(valor);
    if (valor === "" || valor === null || !Number.isInteger(n) || n < 0 || n > maximo) {
      throw new ErroDoPainel(`${rotulo}: use um número inteiro entre 0 e ${maximo.toLocaleString("pt-BR")}.`);
    }
    return n;
  };
  const dinheiro = (valor: unknown, rotulo: string, casas: number) => {
    const n = decimalDoCampo(valor);
    if (!Number.isFinite(n) || n < 0 || n > 1000000) throw new ErroDoPainel(`${rotulo}: valor inválido.`);
    const fator = 10 ** casas;
    return Math.round(n * fator) / fator;
  };
  const atualizacao = {
    limite_emissores: inteiro(r.limite_emissores, "Vagas de empresas emissoras", 100000),
    notas_teste_por_mes: inteiro(r.notas_teste_por_mes, "Notas de teste por mês", 100000),
    aviso_saldo_baixo: inteiro(r.aviso_saldo_baixo, "Aviso de saldo baixo", 100000),
    plano_spedy_notas: inteiro(r.plano_spedy_notas, "Notas do plano da Spedy", 10000000),
    plano_spedy_valor: dinheiro(r.plano_spedy_valor, "Valor do plano da Spedy", 2),
    plano_spedy_nota_excedente: dinheiro(r.plano_spedy_nota_excedente, "Nota excedente", 4),
    vendas_abertas: r.vendas_abertas !== false,
    updated_at: new Date().toISOString(),
    updated_by: email,
  };
  const { error } = await admin.from("fiscal_regras").upsert({ id: 1, ...atualizacao }, { onConflict: "id" });
  if (error) throw new Error(error.message);
}

/** Notas de presente (quantidade positiva) ou retiradas (negativa) no mês corrente. */
export async function darCortesia(
  admin: Admin,
  input: { empresaId: string; ambiente: AmbienteFiscal; quantidade: unknown; descricao: unknown; ator: string | null }
) {
  const quantidade = Number(input.quantidade);
  if (!Number.isInteger(quantidade) || quantidade === 0 || Math.abs(quantidade) > 100000) {
    throw new ErroDoPainel("Informe quantas notas dar (ou tirar, com sinal de menos).");
  }
  const descricao = texto(input.descricao).slice(0, 140);
  if (descricao.length < 3) throw new ErroDoPainel("Escreva o motivo: ele aparece no extrato da empresa.");

  const { data: emissor, error: erroDoEmissor } = await admin
    .from("fiscal_emissores")
    .select("id")
    .eq("empresa_id", input.empresaId)
    .eq("ambiente", input.ambiente)
    .maybeSingle();
  if (erroDoEmissor) throw new Error(erroDoEmissor.message);
  if (!emissor) throw new ErroDoPainel("Esta empresa não tem emissão configurada neste ambiente.", 404);

  if (quantidade < 0) {
    const { data: saldo, error } = await admin.rpc("fiscal_saldo", {
      p_empresa: input.empresaId,
      p_ambiente: input.ambiente,
      p_competencia: competenciaAtual(),
    });
    if (error) throw new Error(error.message);
    const disponiveis = Number(objeto(saldo).disponiveis ?? 0);
    if (disponiveis + quantidade < 0) {
      throw new ErroDoPainel(
        disponiveis > 0
          ? `Dá para tirar no máximo ${disponiveis} ${disponiveis === 1 ? "nota" : "notas"}: é o que está disponível agora.`
          : "Esta empresa não tem notas disponíveis para tirar."
      );
    }
  }

  const { data, error } = await admin.rpc("fiscal_cortesia", {
    p_empresa: input.empresaId,
    p_ambiente: input.ambiente,
    p_quantidade: quantidade,
    p_descricao: descricao,
    p_ator: input.ator,
  });
  if (error) throw new Error(error.message);
  return data;
}

/**
 * Suspender corta a emissão na hora (o admin recusa e a fila desiste). Reativar
 * devolve o status que a configuração merece, com a mesma regra do admin:
 * empresa na Spedy, certificado no prazo, um modelo ligado e a tributação que
 * esse modelo usa.
 */
export async function mudarSituacaoDoEmissor(admin: Admin, id: string, acao: unknown) {
  const { data, error } = await admin.from("fiscal_emissores").select(CAMPOS_DO_EMISSOR).eq("id", id).maybeSingle();
  if (error) throw new Error(error.message);
  const emissor = data as LinhaDoEmissor | null;
  if (!emissor) throw new ErroDoPainel("Empresa emissora não encontrada.", 404);

  let status: "ativo" | "rascunho" | "suspenso";
  if (acao === "suspender") {
    status = "suspenso";
  } else if (acao === "reativar") {
    const { data: perfis, error: erroDosPerfis } = await admin
      .from("fiscal_perfis")
      .select("tipo")
      .eq("empresa_id", emissor.empresa_id)
      .eq("ativo", true);
    if (erroDosPerfis) throw new Error(erroDosPerfis.message);
    const tipos = new Set(((perfis ?? []) as Array<{ tipo: string }>).map((p) => p.tipo));
    const nfe = objeto(emissor.nfe).ativo === true;
    const nfce = objeto(emissor.nfce).ativo === true;
    const nfse = objeto(emissor.nfse).ativo === true;
    const certificado = objeto(emissor.certificado);
    const dias = diasParaVencer((typeof certificado.validade === "string" ? certificado.validade : null) ?? emissor.certificado_validade);
    const pronto =
      Boolean(emissor.spedy_empresa_id && emissor.chave_cifrada) &&
      Boolean(certificado.id) &&
      dias !== null &&
      dias >= 0 &&
      (nfe || nfce || nfse) &&
      (!(nfe || nfce) || tipos.has("mercadoria")) &&
      (!nfse || tipos.has("servico"));
    status = pronto ? "ativo" : "rascunho";
  } else {
    throw new ErroDoPainel("Ação desconhecida.");
  }

  const { error: erroAoGravar } = await admin
    .from("fiscal_emissores")
    .update({ status, updated_at: new Date().toISOString() })
    .eq("id", id);
  if (erroAoGravar) throw new Error(erroAoGravar.message);
  return status;
}
