import { createAdminClient } from "@/lib/supabase-server";
import {
  getUltimoSyncDePedidosXbz,
  insertXbzLog,
  listActiveXbzFornecedores,
  listPedidosAbertosParaSync,
  upsertXbzPedido,
} from "@/lib/xbz/xbz.repository";
import { mapearStatusXbz, requestXbz } from "@/lib/xbz/xbz.service";
import { statusDoPedidoXbz, valorDoPedidoXbz } from "@/lib/xbz/xbz.pedido";

function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function normalizeRecord(value: unknown) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
  return value as Record<string, unknown>;
}

function normalizeRecordArray(value: unknown) {
  if (!Array.isArray(value)) return [];
  return value.filter((item) => item && typeof item === "object") as Array<Record<string, unknown>>;
}

/* `ownerId` limita a varredura a uma empresa. Sem ele, roda para a plataforma
   inteira — o que só o relógio de madrugada tem motivo para fazer. */
export async function syncPedidosXbz(ownerId?: string) {
  const admin = createAdminClient();
  const fornecedores = await listActiveXbzFornecedores(admin, ownerId);
  let processed = 0;
  let updated = 0;
  const errors: Array<{ fornecedorId: string; numeroXbz: string; error: string }> = [];

  for (const fornecedor of fornecedores) {
    const abertos = await listPedidosAbertosParaSync(admin, fornecedor.id);
    /* Quem não tem pedido esperando resposta não consome nada da cota diária
       daquele CNPJ: a XBZ nem chega a ser chamada. */
    if (abertos.length === 0) continue;

    /* Uma consulta por empresa, e não uma por pedido: a lista dos últimos
       120 dias traz todos de uma vez. Com dez pedidos em aberto, eram dez das
       24 consultas do dia gastas de madrugada. */
    let daXbz: Map<string, Record<string, unknown>>;
    try {
      const params = new URLSearchParams({ qtd_dias: "120", exibir_finalizados: "true", exibir_cancelados: "true" });
      const raw = await requestXbz(fornecedor.id, `/PedidosListar?${params.toString()}`, "GET", {
        cnpj: fornecedor.cnpj,
        token: fornecedor.token,
      });
      daXbz = new Map(
        normalizeRecordArray(raw)
          .map((item) => [String(item.numero ?? "").trim(), item] as const)
          .filter(([numero]) => Boolean(numero))
      );
    } catch (error) {
      const message = error instanceof Error ? error.message : "Erro ao sincronizar pedidos.";
      for (const pedido of abertos) if (pedido.numeroXbz) errors.push({ fornecedorId: fornecedor.id, numeroXbz: pedido.numeroXbz, error: message });
      await insertXbzLog(admin, {
        fornecedorId: fornecedor.id,
        action: "syncPedidosXbz",
        payload: { pedidos: abertos.length, failed: true },
        response: { error: message },
      }).catch(() => null);
      await sleep(500);
      continue;
    }

    const resultado: Array<{ numero: string; statusXbz: string | null; erro?: string }> = [];
    for (const pedido of abertos) {
      if (!pedido.numeroXbz) continue;
      processed += 1;
      try {
        /* Pedido mais velho que a lista: fica como está. */
        const first = normalizeRecord(daXbz.get(pedido.numeroXbz));
        if (!Object.keys(first).length) continue;
        const statusXbz = statusDoPedidoXbz(first) || pedido.statusXbz || "";

        /* Sem `payloadEnvio` e sem `pedidoLocalId`: a gravação mantém o que o
           pedido já tinha (o que foi enviado e o pedido de origem). */
        await upsertXbzPedido(admin, {
          fornecedorId: fornecedor.id,
          numeroXbz: pedido.numeroXbz,
          statusXbz: statusXbz || null,
          statusMapeado: statusXbz ? mapearStatusXbz(statusXbz) : pedido.statusMapeado,
          valorTotal: valorDoPedidoXbz(first) ?? pedido.valorTotal ?? null,
          payloadRetorno: first,
        });
        resultado.push({ numero: pedido.numeroXbz, statusXbz: statusXbz || null });
        updated += 1;
      } catch (error) {
        const message = error instanceof Error ? error.message : "Erro ao sincronizar pedido.";
        errors.push({ fornecedorId: fornecedor.id, numeroXbz: pedido.numeroXbz, error: message });
        resultado.push({ numero: pedido.numeroXbz, statusXbz: null, erro: message.slice(0, 200) });
      }
    }

    /* Um registro por empresa: vários seguidos no mesmo milissegundo batiam
       na chave única do registro e um apagava o outro. */
    await insertXbzLog(admin, {
      fornecedorId: fornecedor.id,
      action: "syncPedidosXbz",
      payload: { pedidos: abertos.length },
      response: { pedidos: resultado.slice(0, 100) },
    }).catch(() => null);
    await sleep(500);
  }

  return {
    fornecedores: fornecedores.length,
    processed,
    updated,
    errors,
  };
}

declare global {
  var __xbzSyncIntervalStarted: boolean | undefined;
  var __xbzUltimoDiaSincronizado: string | undefined;
}

/* Das 5h às 9h de São Paulo: o dono da gráfica chega e já encontra o status
   de ontem atualizado, e a varredura não briga por cota com quem está usando
   a tela. É janela, e não "assim que der", para um restart no meio da tarde
   não virar uma varredura fora de hora gastando a cota do dia. Se o servidor
   estiver fora do ar a manhã inteira, o dia passa sem varredura automática —
   a tela tem o botão de atualizar status para esse caso. */
const HORA_INICIAL_DA_VARREDURA = 5;
const HORA_FINAL_DA_VARREDURA = 9;
const INTERVALO_DE_CHECAGEM_MS = 15 * 60 * 1000;

/* O Brasil não tem mais horário de verão, então -3h descreve São Paulo o ano
   inteiro — e o servidor pode estar em qualquer fuso. */
function emSaoPaulo(referencia = new Date()) {
  const local = new Date(referencia.getTime() - 3 * 60 * 60 * 1000);
  return { dia: local.toISOString().slice(0, 10), hora: local.getUTCHours() };
}

async function varrerUmaVezPorDia() {
  const { dia, hora } = emSaoPaulo();
  if (globalThis.__xbzUltimoDiaSincronizado === dia) return;
  if (hora < HORA_INICIAL_DA_VARREDURA || hora >= HORA_FINAL_DA_VARREDURA) return;

  try {
    /* Deploy e restart acontecem várias vezes por dia e apagam a marca da
       memória. O último registro de sincronização no banco é o que sobrevive
       e impede repetir a varredura no mesmo dia. */
    const ultimo = await getUltimoSyncDePedidosXbz(createAdminClient());
    if (ultimo && emSaoPaulo(new Date(ultimo)).dia === dia) {
      globalThis.__xbzUltimoDiaSincronizado = dia;
      return;
    }
  } catch {
    /* Sem resposta do banco não dá para saber se já rodou hoje. Melhor não
       arriscar gastar a cota duas vezes: tenta de novo no próximo intervalo. */
    return;
  }

  /* A marca é gravada ANTES de sair chamando a XBZ. Se a varredura falhar no
     meio, o dia não recomeça a cada 15 minutos queimando o que sobrou da
     cota — fica para amanhã, e a tela tem o botão de atualizar status. */
  globalThis.__xbzUltimoDiaSincronizado = dia;
  await syncPedidosXbz().catch(() => null);
}

/*
  O relógio da sincronização automática.

  Ele acordava a cada 10 minutos e já no boot, varrendo todos os fornecedores
  de todos os tenants: 144 varreduras por dia contra um fornecedor que aceita
  24 chamadas por CNPJ por dia. A cota do dono da gráfica acabava antes de ele
  abrir a tela, e o contador que ele lia não tinha relação com o que ele fez.

  Agora o intervalo curto só olha as horas (não custa nada, não chama a XBZ) e
  quem gasta chamada é uma única passada por dia, de madrugada.
*/
export function startXbzSyncCron() {
  if (process.env.NODE_ENV === "test") return;
  if (globalThis.__xbzSyncIntervalStarted) return;
  globalThis.__xbzSyncIntervalStarted = true;
  setInterval(() => {
    void varrerUmaVezPorDia();
  }, INTERVALO_DE_CHECAGEM_MS);
}
