import { ambienteFiscal, type AmbienteFiscal } from "@/lib/fiscal/ambiente";
import { criarWebhook, habilitarWebhook, listarWebhooks, segredoDosWebhooks, type SpedyNota } from "@/lib/spedy/api";
import { webhookValido, type CabecalhosDoWebhook } from "@/lib/spedy/webhook";

import { spedyDaTitular, type Admin } from "./base";
import { aplicarNotaDaSpedy, type NotaGuardada } from "./notas";

/**
 * Os avisos (webhooks) da Spedy: um por conta, para todas as empresas.
 *
 * O segredo que assina os avisos é da conta e pode ser lido quantas vezes for
 * preciso. Fica em memória por 10 minutos; se uma assinatura não confere, lê
 * de novo uma vez (o segredo pode ter sido trocado).
 */

export const EVENTO_DA_SPEDY = "invoice.status_changed";

type CacheDoSegredo = { valor: string; lidoEm: number };

declare global {
  var __segredoDosAvisosDaSpedy: Partial<Record<AmbienteFiscal, CacheDoSegredo>> | undefined;
}

async function segredo(ambiente: AmbienteFiscal, forcar = false): Promise<string | null> {
  globalThis.__segredoDosAvisosDaSpedy ??= {};
  const cache = globalThis.__segredoDosAvisosDaSpedy[ambiente];
  if (!forcar && cache && Date.now() - cache.lidoEm < 10 * 60 * 1000) return cache.valor;
  try {
    const resposta = await segredoDosWebhooks(spedyDaTitular(ambiente));
    const valor = String(resposta?.secret || "").trim();
    if (!valor) return null;
    globalThis.__segredoDosAvisosDaSpedy[ambiente] = { valor, lidoEm: Date.now() };
    return valor;
  } catch (erro) {
    console.error("[fiscal] não foi possível ler o segredo dos avisos da Spedy", erro instanceof Error ? erro.message : erro);
    return cache?.valor ?? null;
  }
}

export async function avisoDaSpedyAutentico(cabecalhos: CabecalhosDoWebhook, corpoCru: string) {
  const ambiente = ambienteFiscal();
  const atual = await segredo(ambiente);
  if (atual && webhookValido([atual], cabecalhos, corpoCru)) return true;
  const renovado = await segredo(ambiente, true);
  return Boolean(renovado && renovado !== atual && webhookValido([renovado], cabecalhos, corpoCru));
}

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

export async function processarAvisoDaSpedy(admin: Admin, aviso: { id: string; event: string; data: unknown }) {
  const { data: jaVisto } = await admin.from("fiscal_webhooks").select("id,processado_em").eq("id", aviso.id).maybeSingle();
  if (jaVisto?.processado_em) return { resultado: "repetido" as const };
  if (!jaVisto) {
    await admin
      .from("fiscal_webhooks")
      .upsert({ id: aviso.id, origem: "spedy", evento: aviso.event, payload: aviso.data as object }, { onConflict: "id", ignoreDuplicates: true });
  }

  try {
    if (!aviso.event.startsWith("invoice.")) {
      await admin.from("fiscal_webhooks").update({ processado_em: new Date().toISOString(), erro: "evento ignorado" }).eq("id", aviso.id);
      return { resultado: "ignorado" as const };
    }
    const nota = aviso.data as SpedyNota;
    let registro: NotaGuardada | null = null;
    if (nota.integrationId && UUID.test(nota.integrationId)) {
      const { data } = await admin.from("fiscal_notas").select("*").eq("id", nota.integrationId).maybeSingle();
      registro = (data as NotaGuardada | null) ?? null;
    }
    if (!registro && nota.id && UUID.test(nota.id)) {
      const { data } = await admin.from("fiscal_notas").select("*").eq("spedy_nota_id", nota.id).maybeSingle();
      registro = (data as NotaGuardada | null) ?? null;
    }
    if (!registro || registro.ambiente !== ambienteFiscal()) {
      await admin.from("fiscal_webhooks").update({ processado_em: new Date().toISOString(), erro: "nota não encontrada" }).eq("id", aviso.id);
      return { resultado: "nota_desconhecida" as const };
    }
    await aplicarNotaDaSpedy(admin, registro, nota);
    await admin.from("fiscal_webhooks").update({ processado_em: new Date().toISOString(), erro: null }).eq("id", aviso.id);
    return { resultado: "aplicado" as const };
  } catch (erro) {
    const mensagem = erro instanceof Error ? erro.message : "falha";
    await admin.from("fiscal_webhooks").update({ erro: mensagem }).eq("id", aviso.id);
    throw erro;
  }
}

/** Owner: confere que o aviso da conta aponta para este servidor e está ligado. */
export async function garantirAvisoDaSpedy(ambiente: AmbienteFiscal, urlBase: string) {
  const url = `${urlBase.replace(/\/+$/, "")}/api/webhooks/spedy`;
  if (!url.startsWith("https://")) {
    return { situacao: "sem_https" as const, url, mensagem: "Avisos só funcionam com endereço https. Neste servidor a fila consulta as notas sozinha." };
  }
  const ctx = spedyDaTitular(ambiente);
  const todos = await listarWebhooks(ctx);
  const meu = todos.find((webhook) => webhook.event === EVENTO_DA_SPEDY && webhook.url === url);
  if (!meu) {
    const criado = await criarWebhook(ctx, EVENTO_DA_SPEDY, url);
    return { situacao: "criado" as const, url, id: criado.id };
  }
  if (meu.enabled === false) {
    await habilitarWebhook(ctx, meu.id);
    return { situacao: "religado" as const, url, id: meu.id };
  }
  return { situacao: "ok" as const, url, id: meu.id };
}

export async function situacaoDoAvisoDaSpedy(ambiente: AmbienteFiscal, urlBase: string) {
  const url = `${urlBase.replace(/\/+$/, "")}/api/webhooks/spedy`;
  const todos = await listarWebhooks(spedyDaTitular(ambiente));
  const meu = todos.find((webhook) => webhook.event === EVENTO_DA_SPEDY && webhook.url === url);
  return { url, existe: Boolean(meu), ligado: meu ? meu.enabled !== false : false, outros: todos.filter((w) => w !== meu).length };
}
