import { createClient } from "../../src/compat/supabase.js";
import { Context } from "hono";
import * as kv from "./kv_store.tsx";
import { validarAcessoEmpresaRequest, validarSuperAdminTonlineRequest } from "./admin-shared-helpers.tsx";

const BACKUP_BUCKET = "make-7249dcd9-backups";
const KV_TABLE = "kv_store_7249dcd9";
const PAGE_SIZE = 1000;
const BACKUP_FILE_LIMIT_BYTES = 1024 * 1024 * 50;

const supabase = createClient(
  Deno.env.get("SUPABASE_URL") ?? "",
  Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
);

type KvRow = {
  key: string;
  value: any;
};

type BackupMeta = {
  id: string;
  empresaId: string;
  criadoEm: string;
  criadoPor: string;
  automatico: boolean;
  dataReferencia: string;
  objectPath: string;
  fileName: string;
  tamanhoBytes: number;
  hashSha256: string;
  itemCount: number;
  status: "concluido" | "restaurado";
  ultimaRestauracaoEm?: string;
  ultimaRestauracaoPor?: string;
};

async function ensureBackupBucket() {
  const { data: buckets, error: listError } = await supabase.storage.listBuckets();
  if (listError) throw listError;

  const exists = buckets?.some((bucket) => bucket.name === BACKUP_BUCKET);

  if (!exists) {
    const { error } = await supabase.storage.createBucket(BACKUP_BUCKET, {
      public: false,
      fileSizeLimit: BACKUP_FILE_LIMIT_BYTES,
    });
    if (error && !error.message?.includes("already exists")) throw error;
    return;
  }

  await supabase.storage.updateBucket(BACKUP_BUCKET, {
    public: false,
    fileSizeLimit: BACKUP_FILE_LIMIT_BYTES,
  });
}

async function lerTodasLinhasKv() {
  const linhas: KvRow[] = [];
  let offset = 0;

  while (true) {
    const { data, error } = await supabase
      .from(KV_TABLE)
      .select("key,value")
      .order("key", { ascending: true })
      .range(offset, offset + PAGE_SIZE - 1);

    if (error) throw error;

    const pagina = (data || []) as KvRow[];
    linhas.push(...pagina);

    if (pagina.length < PAGE_SIZE) break;
    offset += PAGE_SIZE;
  }

  return linhas;
}

function isBackupInternalKey(key: string) {
  return (
    key.startsWith("backup_meta:") ||
    key.startsWith("backup_restore:") ||
    key.startsWith("backup_lock:")
  );
}

function keyPertenceEmpresa(key: string, empresaId: string) {
  return (
    key === `empresa:${empresaId}` ||
    key.startsWith(`${empresaId}:`) ||
    key.includes(`:${empresaId}:`) ||
    key.endsWith(`:${empresaId}`)
  );
}

function valuePertenceEmpresa(value: any, empresaId: string) {
  const stack = [value];
  let visited = 0;

  while (stack.length && visited < 600) {
    const current = stack.pop();
    visited += 1;

    if (!current || typeof current !== "object") continue;

    if (Array.isArray(current)) {
      stack.push(...current);
      continue;
    }

    for (const [field, raw] of Object.entries(current)) {
      const fieldName = field.toLowerCase();
      const isEmpresaField =
        fieldName === "empresaid" ||
        fieldName === "empresa_id" ||
        fieldName === "empresa" ||
        fieldName === "companyid";

      if (isEmpresaField && String(raw || "") === empresaId) return true;

      if (raw && typeof raw === "object") {
        stack.push(raw);
      }
    }
  }

  return false;
}

function linhaPertenceEmpresa(row: KvRow, empresaId: string) {
  if (isBackupInternalKey(row.key)) return false;
  return keyPertenceEmpresa(row.key, empresaId) || valuePertenceEmpresa(row.value, empresaId);
}

async function sha256(text: string) {
  const hashBuffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
  return Array.from(new Uint8Array(hashBuffer))
    .map((byte) => byte.toString(16).padStart(2, "0"))
    .join("");
}

async function gzipText(text: string) {
  const stream = new Blob([text], { type: "application/json" }).stream();
  const compressedStream = stream.pipeThrough(new CompressionStream("gzip"));
  return await new Response(compressedStream).blob();
}

async function readBackupText(data: Blob, fileName?: string) {
  if (fileName?.endsWith(".gz")) {
    const decompressedStream = data.stream().pipeThrough(new DecompressionStream("gzip"));
    return await new Response(decompressedStream).text();
  }

  return await data.text();
}

function sanitizeSegment(value: string) {
  return String(value || "empresa")
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-zA-Z0-9._-]+/g, "-")
    .replace(/-+/g, "-")
    .replace(/^-|-$/g, "")
    .slice(0, 80) || "empresa";
}

function getMaputoDateString(date = new Date()) {
  const parts = new Intl.DateTimeFormat("en-CA", {
    timeZone: "Africa/Maputo",
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
  }).formatToParts(date);
  const map = Object.fromEntries(parts.map((part) => [part.type, part.value]));
  return `${map.year}-${map.month}-${map.day}`;
}

function metaKey(empresaId: string, backupId: string) {
  return `backup_meta:${empresaId}:${backupId}`;
}

async function criarBackupEmpresaComLinhas(
  empresaId: string,
  todasLinhas: KvRow[],
  options: { automatico?: boolean; criadoPor?: string; dataReferencia?: string } = {},
) {
  if (!empresaId) throw new Error("empresaId nao fornecido");

  const items = todasLinhas
    .filter((row) => linhaPertenceEmpresa(row, empresaId))
    .map((row) => ({ key: row.key, value: row.value }));

  const agora = new Date();
  const dataReferencia = options.dataReferencia || getMaputoDateString(agora);
  const backupId = `bkp_${dataReferencia.replace(/-/g, "")}_${agora.getTime()}`;
  const payload = {
    schemaVersion: 1,
    source: KV_TABLE,
    empresaId,
    generatedAt: agora.toISOString(),
    itemCount: items.length,
    items,
  };

  const conteudo = JSON.stringify(payload, null, 2);
  const hashSha256 = await sha256(conteudo);
  const arquivo = await gzipText(conteudo);
  const fileName = `${backupId}.json.gz`;
  const objectPath = `${sanitizeSegment(empresaId)}/${dataReferencia}/${fileName}`;

  const { error: uploadError } = await supabase.storage
    .from(BACKUP_BUCKET)
    .upload(objectPath, arquivo, {
      contentType: "application/gzip",
      cacheControl: "0",
      upsert: false,
    });

  if (uploadError) throw uploadError;

  const meta: BackupMeta = {
    id: backupId,
    empresaId,
    criadoEm: agora.toISOString(),
    criadoPor: options.criadoPor || "utilizador",
    automatico: Boolean(options.automatico),
    dataReferencia,
    objectPath,
    fileName,
    tamanhoBytes: arquivo.size,
    hashSha256,
    itemCount: items.length,
    status: "concluido",
  };

  await kv.set(metaKey(empresaId, backupId), meta);
  return meta;
}

async function criarBackupEmpresa(
  empresaId: string,
  options: { automatico?: boolean; criadoPor?: string; dataReferencia?: string } = {},
) {
  await ensureBackupBucket();
  const todasLinhas = await lerTodasLinhasKv();
  return await criarBackupEmpresaComLinhas(empresaId, todasLinhas, options);
}

async function lerBackupPayload(meta: BackupMeta) {
  const { data, error } = await supabase.storage.from(BACKUP_BUCKET).download(meta.objectPath);
  if (error) throw error;
  if (!data) throw new Error("Arquivo de backup nao encontrado");

  const texto = await readBackupText(data, meta.fileName);
  const hashAtual = await sha256(texto);

  if (meta.hashSha256 && hashAtual !== meta.hashSha256) {
    throw new Error("Integridade do backup falhou: hash diferente do registrado");
  }

  const payload = JSON.parse(texto);
  if (payload?.schemaVersion !== 1 || !Array.isArray(payload.items)) {
    throw new Error("Formato de backup invalido");
  }

  return payload as { empresaId: string; itemCount: number; items: KvRow[] };
}

async function listarMetas(empresaId: string) {
  const metas = await kv.getByPrefixWithKeys<BackupMeta>(`backup_meta:${empresaId}:`);
  return metas
    .map((item) => item.value)
    .filter(Boolean)
    .sort((a, b) => String(b.criadoEm).localeCompare(String(a.criadoEm)));
}

async function listarTodasMetas() {
  const metas = await kv.getByPrefixWithKeys<BackupMeta>("backup_meta:");
  return metas
    .map((item) => item.value)
    .filter(Boolean)
    .sort((a, b) => String(b.criadoEm).localeCompare(String(a.criadoEm)));
}

function normalizarDigitos(value: unknown) {
  return String(value || "").replace(/\D/g, "");
}

function normalizarTipoFactura(value: unknown) {
  return String(value || "factura").trim().toLowerCase().replace(/-/g, "_");
}

function obterNumeroSequencialDocumento(numeroDocumento: unknown) {
  const texto = String(numeroDocumento || "");
  const matchComAno = texto.match(/(\d+)\s*\/\s*\d{4}/);
  const matchQualquer = texto.match(/\d+/);
  return String(matchComAno?.[1] || matchQualquer?.[0] || "").replace(/^0+/, "") || "0";
}

function obterAnoDocumento(numeroDocumento: unknown, dataEmissao?: unknown) {
  const anoNumero = Number(String(numeroDocumento || "").match(/\/\s*(\d{4})/)?.[1]);
  if (Number.isFinite(anoNumero) && anoNumero > 0) return anoNumero;

  const anoData = Number(String(dataEmissao || "").slice(0, 4));
  if (Number.isFinite(anoData) && anoData > 0) return anoData;

  return new Date().getFullYear();
}

function formatarNumeroFacturaRecuperada(facturaFonte: any, sequencia: string) {
  const ano = obterAnoDocumento(facturaFonte?.numeroDocumento, facturaFonte?.dataEmissao);
  const numero = String(Number(sequencia)).padStart(2, "0");
  return `Factura nº ${numero}/${ano}`;
}

function clonar<T>(value: T): T {
  return JSON.parse(JSON.stringify(value ?? null));
}

function gerarIdDocumento(prefixo: string) {
  return `${prefixo}-${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
}

async function encontrarFacturaPorNumero(empresaId: string, numero: string) {
  const sequencia = obterNumeroSequencialDocumento(numero);
  const facturas = await kv.getByPrefixWithKeys<any>(`factura:${empresaId}:`);

  return facturas.find((row) => {
    const value = row.value || {};
    return normalizarTipoFactura(value.tipoDocumento) === "factura" &&
      obterNumeroSequencialDocumento(value.numeroDocumento) === sequencia;
  }) || null;
}

async function gerarNumeroNotaCreditoTecnica(
  empresaId: string,
  ano: number,
  sequenciasReservadas: Set<string> = new Set(),
) {
  const notas = await kv.getByPrefixWithKeys<any>(`nota:${empresaId}:`);
  let maior = 0;

  for (const row of notas) {
    const nota = row.value || {};
    if (nota.tipo !== "credito") continue;
    const numero = String(nota.numeroDocumento || "");
    const anoNota = obterAnoDocumento(numero, nota.dataEmissao);
    if (anoNota !== ano) continue;

    const sequencia = Number(obterNumeroSequencialDocumento(numero));
    if (Number.isFinite(sequencia) && sequencia > maior) maior = sequencia;
  }

  for (const sequenciaReservada of sequenciasReservadas) {
    const [anoReservado, sequencia] = sequenciaReservada.split(":");
    if (Number(anoReservado) !== ano) continue;
    const numero = Number(sequencia);
    if (Number.isFinite(numero) && numero > maior) maior = numero;
  }

  const proximaSequencia = maior + 1;
  sequenciasReservadas.add(`${ano}:${proximaSequencia}`);

  return `Nota de Crédito nº ${String(proximaSequencia).padStart(2, "0")}/${ano}`;
}

function construirNotaCreditoTecnica(facturaAnulada: any, numeroDocumento: string, motivo: string) {
  const itens = clonar(facturaAnulada.itens || facturaAnulada.linhas || []);
  const dataEmissao = facturaAnulada.dataEmissao || new Date().toISOString().slice(0, 10);
  const agora = new Date().toISOString();

  return {
    empresaId: facturaAnulada.empresaId,
    empresaSnapshot: facturaAnulada.empresaSnapshot,
    exercicioEconomicoId: facturaAnulada.exercicioEconomicoId,
    anoExercicio: facturaAnulada.anoExercicio || obterAnoDocumento(facturaAnulada.numeroDocumento, dataEmissao),
    tipo: "credito",
    tipoDocumento: "nota-credito",
    numeroDocumento,
    dataEmissao,
    estado: "emitida",
    clienteId: facturaAnulada.clienteId,
    cliente: clonar(facturaAnulada.cliente || {}),
    moeda: facturaAnulada.moeda || "MZN",
    condicaoPagamento: facturaAnulada.condicaoPagamento || facturaAnulada.condicoesPagamento || "",
    itens,
    subtotal: Number(facturaAnulada.subtotal || facturaAnulada.totalLiquido || 0),
    totalImpostos: Number(facturaAnulada.totalImpostos || facturaAnulada.totalIVA || 0),
    descontos: Number(facturaAnulada.descontos || facturaAnulada.totalDescontos || 0),
    totalGeral: Number(facturaAnulada.totalGeral || 0),
    observacoes: motivo,
    facturaId: facturaAnulada.id,
    facturaReferencia: facturaAnulada.numeroDocumento,
    regularizacaoSequencia: true,
    motivoRegularizacao: motivo,
    naoMovimentaStock: true,
    geraLancamentoContabil: false,
    createdAt: agora,
    updatedAt: agora,
  };
}

function facturaBackupPassaFiltros(value: any, filtros: { clienteNuit?: string; numeros?: Set<string>; tipoDocumento?: string }) {
  if (!value || typeof value !== "object") return false;

  if (filtros.clienteNuit) {
    const nuit = normalizarDigitos(value.cliente?.nuit || value.clienteNuit || value.nuit);
    if (nuit !== filtros.clienteNuit) return false;
  }

  if (filtros.tipoDocumento) {
    if (normalizarTipoFactura(value.tipoDocumento) !== filtros.tipoDocumento) return false;
  }

  if (filtros.numeros && filtros.numeros.size > 0) {
    const numeroSequencial = obterNumeroSequencialDocumento(value.numeroDocumento);
    const numeroCompleto = String(value.numeroDocumento || "").trim().toLowerCase();
    if (!filtros.numeros.has(numeroSequencial) && !filtros.numeros.has(numeroCompleto)) {
      return false;
    }
  }

  return true;
}

function resumirFacturaRecuperacao(item: KvRow, meta: BackupMeta) {
  const value = item.value || {};
  return {
    key: item.key,
    backupId: meta.id,
    backupData: meta.dataReferencia,
    empresaId: value.empresaId || meta.empresaId,
    id: value.id,
    tipoDocumento: value.tipoDocumento || "factura",
    numeroDocumento: value.numeroDocumento,
    estado: value.estado,
    clienteNome: value.cliente?.nome || value.clienteNome || "",
    clienteNuit: value.cliente?.nuit || value.clienteNuit || "",
    dataEmissao: value.dataEmissao,
    totalGeral: value.totalGeral,
  };
}

async function executarRecuperacaoFacturas(
  metas: BackupMeta[],
  body: any,
  empresaIdFiltro?: string,
) {
  const confirmar = body?.confirmar === true;
  const clienteNuit = normalizarDigitos(body?.clienteNuit || body?.nuit);
  const empresaNuit = normalizarDigitos(body?.empresaNuit);
  const empresaIdBody = String(body?.empresaId || "").trim();
  const tipoDocumento = body?.tipoDocumento ? normalizarTipoFactura(body.tipoDocumento) : undefined;
  const datasReferencia = Array.isArray(body?.datasReferencia)
    ? new Set(body.datasReferencia.map((data: unknown) => String(data || "").trim()).filter(Boolean))
    : body?.dataReferencia || body?.dataBackup
      ? new Set([String(body.dataReferencia || body.dataBackup).trim()].filter(Boolean))
      : undefined;
  let empresasPermitidas: Set<string> | undefined = empresaIdFiltro || empresaIdBody
    ? new Set([empresaIdFiltro || empresaIdBody])
    : undefined;

  if (!empresasPermitidas && empresaNuit) {
    const empresas = await kv.getByPrefixWithKeys<any>("empresa:");
    const ids = empresas
      .map((row) => row.value)
      .filter((empresa) => normalizarDigitos(empresa?.nuit) === empresaNuit)
      .map((empresa) => String(empresa?.id || "").trim())
      .filter(Boolean);
    empresasPermitidas = new Set(ids);
  }

  const numeros = Array.isArray(body?.numeros)
    ? new Set(
        body.numeros
          .map((numero: unknown) => String(numero || "").trim().toLowerCase())
          .filter(Boolean)
          .flatMap((numero: string) => [numero, numero.replace(/^0+/, "") || "0"]),
      )
    : undefined;

  const metasFiltradas = metas.filter((meta) => {
    if (!meta?.empresaId || !meta?.id) return false;
    if (empresasPermitidas && !empresasPermitidas.has(meta.empresaId)) return false;
    if (datasReferencia && !datasReferencia.has(meta.dataReferencia)) return false;
    if (body?.backupId && meta.id !== body.backupId) return false;
    return true;
  });

  const prefixCurrent = empresasPermitidas && empresasPermitidas.size === 1
    ? `factura:${Array.from(empresasPermitidas)[0]}:`
    : "factura:";
  const facturasAtuais = await kv.getByPrefixWithKeys<any>(prefixCurrent);
  const chavesActuais = new Set(facturasAtuais.map((row) => row.key));
  const candidatos = new Map<string, { item: KvRow; meta: BackupMeta }>();

  for (const meta of metasFiltradas) {
    const payload = await lerBackupPayload(meta);
    for (const item of payload.items) {
      if (!item?.key?.startsWith("factura:")) continue;
      if (empresaIdFiltro && !item.key.startsWith(`factura:${empresaIdFiltro}:`)) continue;
      if (chavesActuais.has(item.key) || candidatos.has(item.key)) continue;
      if (!facturaBackupPassaFiltros(item.value, { clienteNuit, numeros, tipoDocumento })) continue;
      candidatos.set(item.key, { item, meta });
    }
  }

  const itensARepor = Array.from(candidatos.values()).map(({ item }) => item);
  if (confirmar && itensARepor.length > 0) {
    await msetEmLotes(itensARepor);
    await kv.set(`backup_restore:facturas:${Date.now()}`, {
      tipo: "recuperacao_facturas_selectiva",
      empresaId: empresaIdFiltro || "todas",
      confirmadoEm: new Date().toISOString(),
      filtros: {
        empresaNuit,
        clienteNuit,
        tipoDocumento,
        datasReferencia: datasReferencia ? Array.from(datasReferencia) : undefined,
        numeros: numeros ? Array.from(numeros) : undefined,
        backupId: body?.backupId,
      },
      itensRestaurados: itensARepor.length,
      chaves: itensARepor.map((item) => item.key),
    });
  }

  return {
    confirmado: confirmar,
    empresasFiltradas: empresasPermitidas ? Array.from(empresasPermitidas) : undefined,
    backupsAnalisados: metasFiltradas.length,
    backupsSelecionados: metasFiltradas.map((meta) => ({
      id: meta.id,
      empresaId: meta.empresaId,
      dataReferencia: meta.dataReferencia,
      criadoEm: meta.criadoEm,
      itemCount: meta.itemCount,
      tamanhoBytes: meta.tamanhoBytes,
    })),
    encontradas: itensARepor.length,
    facturas: Array.from(candidatos.values()).map(({ item, meta }) => resumirFacturaRecuperacao(item, meta)),
  };
}

async function msetEmLotes(items: KvRow[]) {
  for (let index = 0; index < items.length; index += 100) {
    const lote = items.slice(index, index + 100);
    await kv.mset(
      lote.map((item) => item.key),
      lote.map((item) => item.value),
    );
  }
}

async function mdelEmLotes(keys: string[]) {
  for (let index = 0; index < keys.length; index += 100) {
    const lote = keys.slice(index, index + 100);
    if (lote.length) await kv.mdel(lote);
  }
}

export async function listarBackups(c: Context) {
  try {
    const empresaId = c.req.param("empresaId");
    const acesso = await validarAcessoEmpresaRequest(c, empresaId);
    if (!acesso.permitido) return acesso.resposta;

    const data = await listarMetas(empresaId);
    return c.json({ success: true, data });
  } catch (error) {
    return c.json({ success: false, error: error.message }, 500);
  }
}

export async function criarBackupManual(c: Context) {
  try {
    const empresaId = c.req.param("empresaId");
    const acesso = await validarAcessoEmpresaRequest(c, empresaId);
    if (!acesso.permitido) return acesso.resposta;

    const body = await c.req.json().catch(() => ({}));
    const meta = await criarBackupEmpresa(empresaId, {
      automatico: false,
      criadoPor: body?.criadoPor || "utilizador",
    });

    return c.json({ success: true, data: meta });
  } catch (error) {
    return c.json({ success: false, error: error.message }, 500);
  }
}

export async function recuperarFacturasEmpresa(c: Context) {
  try {
    const empresaId = c.req.param("empresaId");
    const acesso = await validarAcessoEmpresaRequest(c, empresaId);
    if (!acesso.permitido) return acesso.resposta;

    const body = await c.req.json().catch(() => ({}));
    const metas = await listarMetas(empresaId);
    const resultado = await executarRecuperacaoFacturas(metas, body, empresaId);
    return c.json({ success: true, data: resultado });
  } catch (error) {
    return c.json({ success: false, error: error.message }, 500);
  }
}

export async function recuperarFacturasTodasEmpresas(c: Context) {
  try {
    const acesso = await validarSuperAdminTonlineRequest(c);
    if (!acesso.permitido) return acesso.resposta;

    const body = await c.req.json().catch(() => ({}));
    const metas = await listarTodasMetas();
    const resultado = await executarRecuperacaoFacturas(metas, body);
    return c.json({ success: true, data: resultado });
  } catch (error) {
    return c.json({ success: false, error: error.message }, 500);
  }
}

export async function recuperarFacturasPayload(c: Context) {
  try {
    const acesso = await validarSuperAdminTonlineRequest(c);
    if (!acesso.permitido) return acesso.resposta;

    const body = await c.req.json().catch(() => ({}));
    const confirmar = body?.confirmar === true;
    const substituirExistentes = body?.substituirExistentes === true;
    const clienteNuit = normalizarDigitos(body?.clienteNuit || body?.nuit);
    const empresaIdBody = String(body?.empresaId || "").trim();
    const tipoDocumento = body?.tipoDocumento ? normalizarTipoFactura(body.tipoDocumento) : undefined;
    const origem = String(body?.origem || "payload-local").trim() || "payload-local";
    const rawItems = Array.isArray(body?.items)
      ? body.items
      : Array.isArray(body?.payload?.items)
        ? body.payload.items
        : [];

    if (!rawItems.length) {
      return c.json({ success: false, error: "Nenhuma factura enviada para recuperar" }, 400);
    }

    const numeros = Array.isArray(body?.numeros)
      ? new Set(
          body.numeros
            .map((numero: unknown) => String(numero || "").trim().toLowerCase())
            .filter(Boolean)
            .flatMap((numero: string) => [numero, numero.replace(/^0+/, "") || "0"]),
        )
      : undefined;

    const candidatos: KvRow[] = [];
    const ignoradasExistentes: KvRow[] = [];
    const ignoradasFiltro: KvRow[] = [];

    for (const raw of rawItems) {
      const item = raw as KvRow;
      if (!item?.key?.startsWith("factura:") || !item.value) {
        ignoradasFiltro.push(item);
        continue;
      }

      if (empresaIdBody && !item.key.startsWith(`factura:${empresaIdBody}:`)) {
        ignoradasFiltro.push(item);
        continue;
      }

      if (!facturaBackupPassaFiltros(item.value, { clienteNuit, numeros, tipoDocumento })) {
        ignoradasFiltro.push(item);
        continue;
      }

      const existente = await kv.get(item.key);
      if (existente && !substituirExistentes) {
        ignoradasExistentes.push(item);
        continue;
      }

      candidatos.push(item);
    }

    if (confirmar && candidatos.length > 0) {
      await msetEmLotes(candidatos);
      await kv.set(`backup_restore:facturas_payload:${Date.now()}`, {
        tipo: "recuperacao_facturas_payload",
        origem,
        confirmadoEm: new Date().toISOString(),
        filtros: {
          empresaId: empresaIdBody || undefined,
          clienteNuit,
          tipoDocumento,
          numeros: numeros ? Array.from(numeros) : undefined,
          substituirExistentes,
        },
        itensRestaurados: candidatos.length,
        chaves: candidatos.map((item) => item.key),
      });
    }

    const metaPayload: BackupMeta = {
      id: origem,
      empresaId: empresaIdBody || "payload",
      criadoEm: new Date().toISOString(),
      criadoPor: "recuperacao_payload",
      automatico: false,
      dataReferencia: String(body?.dataReferencia || ""),
      objectPath: origem,
      fileName: origem,
      tamanhoBytes: 0,
      hashSha256: "",
      itemCount: rawItems.length,
      status: "concluido",
    };

    return c.json({
      success: true,
      data: {
        confirmado: confirmar,
        origem,
        recebidas: rawItems.length,
        encontradas: candidatos.length,
        ignoradasExistentes: ignoradasExistentes.length,
        ignoradasFiltro: ignoradasFiltro.length,
        facturas: candidatos.map((item) => resumirFacturaRecuperacao(item, metaPayload)),
        existentes: ignoradasExistentes.map((item) => resumirFacturaRecuperacao(item, metaPayload)),
      },
    });
  } catch (error) {
    return c.json({ success: false, error: error.message }, 500);
  }
}

export async function regularizarSequenciaFacturas(c: Context) {
  try {
    const acesso = await validarSuperAdminTonlineRequest(c);
    if (!acesso.permitido) return acesso.resposta;

    const body = await c.req.json().catch(() => ({}));
    const empresaId = String(body?.empresaId || "").trim();
    const confirmar = body?.confirmar === true;
    const substituirAlvo = body?.substituirAlvo === true;
    const pares = Array.isArray(body?.pares) ? body.pares : [];

    if (!empresaId) {
      return c.json({ success: false, error: "empresaId e obrigatorio" }, 400);
    }

    if (pares.length === 0) {
      return c.json({ success: false, error: "Informe ao menos um par numeroAlvo/numeroFonte" }, 400);
    }

    const agora = new Date().toISOString();
    const itensParaGravar: KvRow[] = [];
    const resultados = [];
    const sequenciasNotaCreditoReservadas = new Set<string>();

    for (const par of pares) {
      const numeroAlvo = String(par?.numeroAlvo || par?.alvo || "").trim();
      const numeroFonte = String(par?.numeroFonte || par?.fonte || "").trim();
      const motivo = String(
        par?.motivo ||
        body?.motivo ||
        "Regularizacao tecnica de sequencia fiscal com anulacao por nota de credito",
      ).trim();

      if (!numeroAlvo || !numeroFonte) {
        resultados.push({ numeroAlvo, numeroFonte, status: "erro", erro: "numeroAlvo e numeroFonte sao obrigatorios" });
        continue;
      }

      const fonte = await encontrarFacturaPorNumero(empresaId, numeroFonte);
      if (!fonte?.value) {
        resultados.push({ numeroAlvo, numeroFonte, status: "erro", erro: "Factura fonte nao encontrada" });
        continue;
      }

      const alvoExistente = await encontrarFacturaPorNumero(empresaId, numeroAlvo);
      if (alvoExistente && !substituirAlvo) {
        resultados.push({
          numeroAlvo,
          numeroFonte,
          status: "ignorado",
          erro: "Factura alvo ja existe. Use substituirAlvo=true apenas se tiver certeza.",
          facturaAlvoExistente: alvoExistente.value?.numeroDocumento,
          key: alvoExistente.key,
        });
        continue;
      }

      const facturaFonte = clonar(fonte.value);
      const idAlvo = alvoExistente?.value?.id || gerarIdDocumento("factura");
      const keyAlvo = alvoExistente?.key || `factura:${empresaId}:${idAlvo}`;
      const numeroDocumentoAlvo = formatarNumeroFacturaRecuperada(facturaFonte, numeroAlvo);
      const ano = obterAnoDocumento(numeroDocumentoAlvo, facturaFonte.dataEmissao);
      const numeroNota = await gerarNumeroNotaCreditoTecnica(empresaId, ano, sequenciasNotaCreditoReservadas);
      const idNota = crypto.randomUUID();
      const keyNota = `nota:${empresaId}:${idNota}`;

      const facturaAnulada = {
        ...facturaFonte,
        id: idAlvo,
        empresaId,
        numeroDocumento: numeroDocumentoAlvo,
        tipoDocumento: "factura",
        estado: "anulado",
        dataAtualizacao: agora,
        updatedAt: agora,
        anuladaEm: agora,
        motivoAnulacao: motivo,
        anuladaPorNotaCreditoId: keyNota,
        anuladaPorNotaCreditoNumero: numeroNota,
        regularizacaoSequencia: true,
        numeroFonteRegularizacao: facturaFonte.numeroDocumento,
        facturaFonteRegularizacaoId: facturaFonte.id,
        recuperadoEm: agora,
      };

      const notaCredito = construirNotaCreditoTecnica(facturaAnulada, numeroNota, motivo);

      itensParaGravar.push(
        { key: keyAlvo, value: facturaAnulada },
        { key: keyNota, value: notaCredito },
      );

      resultados.push({
        numeroAlvo: numeroDocumentoAlvo,
        numeroFonte: facturaFonte.numeroDocumento,
        status: confirmar ? "preparado_para_gravar" : "preview",
        facturaKey: keyAlvo,
        notaCreditoKey: keyNota,
        notaCreditoNumero: numeroNota,
        cliente: facturaAnulada.cliente?.nome || "",
        totalGeral: facturaAnulada.totalGeral,
        substituiuExistente: Boolean(alvoExistente),
      });
    }

    if (confirmar && itensParaGravar.length > 0) {
      await msetEmLotes(itensParaGravar);
      await kv.set(`regularizacao_sequencia_facturas:${empresaId}:${Date.now()}`, {
        tipo: "regularizacao_sequencia_facturas",
        empresaId,
        confirmadoEm: agora,
        motivo: body?.motivo,
        substituirAlvo,
        pares,
        itens: resultados,
      });
    }

    return c.json({
      success: true,
      data: {
        confirmado: confirmar,
        empresaId,
        substituirAlvo,
        totalItensPreparados: itensParaGravar.length,
        resultados,
      },
    });
  } catch (error) {
    return c.json({ success: false, error: error.message }, 500);
  }
}

export async function gerarBackupDownloadUrl(c: Context) {
  try {
    const empresaId = c.req.param("empresaId");
    const acesso = await validarAcessoEmpresaRequest(c, empresaId);
    if (!acesso.permitido) return acesso.resposta;

    const backupId = c.req.param("backupId");
    const meta = await kv.get(metaKey(empresaId, backupId)) as BackupMeta | null;

    if (!meta) {
      return c.json({ success: false, error: "Backup nao encontrado" }, 404);
    }

    const { data, error } = await supabase.storage
      .from(BACKUP_BUCKET)
      .createSignedUrl(meta.objectPath, 600, { download: meta.fileName });

    if (error) throw error;

    return c.json({ success: true, data: { signedUrl: data.signedUrl, expiresIn: 600 } });
  } catch (error) {
    return c.json({ success: false, error: error.message }, 500);
  }
}

export async function restaurarBackup(c: Context) {
  try {
    const empresaId = c.req.param("empresaId");
    const acesso = await validarAcessoEmpresaRequest(c, empresaId);
    if (!acesso.permitido) return acesso.resposta;

    const backupId = c.req.param("backupId");
    const body = await c.req.json().catch(() => ({}));
    const confirmar = Boolean(body?.confirmar);
    const restauradoPor = body?.restauradoPor || "utilizador";
    const meta = await kv.get(metaKey(empresaId, backupId)) as BackupMeta | null;

    if (!meta) {
      return c.json({ success: false, error: "Backup nao encontrado" }, 404);
    }

    const payload = await lerBackupPayload(meta);
    if (payload.empresaId !== empresaId) {
      return c.json({ success: false, error: "Backup pertence a outra empresa" }, 400);
    }

    const atuais = (await lerTodasLinhasKv()).filter((row) => linhaPertenceEmpresa(row, empresaId));
    const keysBackup = new Set(payload.items.map((item) => item.key));
    const keysParaRemover = atuais
      .map((row) => row.key)
      .filter((key) => !keysBackup.has(key));

    const preview = {
      backupId,
      empresaId,
      itensNoBackup: payload.items.length,
      itensAtuais: atuais.length,
      itensARepor: payload.items.length,
      itensARemover: keysParaRemover.length,
      primeirasChaves: payload.items.slice(0, 8).map((item) => item.key),
    };

    if (!confirmar) {
      return c.json({ success: true, data: { preview, requerConfirmacao: true } });
    }

    await msetEmLotes(payload.items);
    await mdelEmLotes(keysParaRemover);

    const atualizado: BackupMeta = {
      ...meta,
      status: "restaurado",
      ultimaRestauracaoEm: new Date().toISOString(),
      ultimaRestauracaoPor: restauradoPor,
    };

    await kv.set(metaKey(empresaId, backupId), atualizado);
    await kv.set(`backup_restore:${empresaId}:${Date.now()}`, {
      backupId,
      empresaId,
      restauradoEm: atualizado.ultimaRestauracaoEm,
      restauradoPor,
      preview,
    });

    return c.json({ success: true, data: { ...preview, restaurado: true } });
  } catch (error) {
    return c.json({ success: false, error: error.message }, 500);
  }
}

export async function executarBackupAutomaticoDiario(c: Context) {
  try {
    const configuredSecret = Deno.env.get("BACKUP_CRON_SECRET");
    const providedSecret = c.req.header("X-Backup-Secret");

    if (!configuredSecret) {
      return c.json({
        success: false,
        error: "BACKUP_CRON_SECRET nao configurado no ambiente da funcao",
      }, 500);
    }

    if (providedSecret !== configuredSecret) {
      return c.json({ success: false, error: "Nao autorizado" }, 401);
    }

    const dataReferencia = getMaputoDateString();
    await ensureBackupBucket();
    const todasLinhas = await lerTodasLinhasKv();
    const empresas = todasLinhas
      .filter((row) => row.key.startsWith("empresa:"))
      .map((row) => ({ key: row.key, value: row.value }));
    const resultados = [];

    for (const item of empresas) {
      const empresaId = item.value?.id || item.key.replace(/^empresa:/, "");
      const existentes = await listarMetas(empresaId);
      const jaExisteHoje = existentes.some((meta) => meta.automatico && meta.dataReferencia === dataReferencia);

      if (jaExisteHoje) {
        resultados.push({ empresaId, status: "ignorado", motivo: "backup diario ja existe" });
        continue;
      }

      const meta = await criarBackupEmpresaComLinhas(empresaId, todasLinhas, {
        automatico: true,
        criadoPor: "agendador",
        dataReferencia,
      });
      resultados.push({ empresaId, status: "criado", backupId: meta.id, itemCount: meta.itemCount });
    }

    return c.json({ success: true, data: { dataReferencia, resultados } });
  } catch (error) {
    return c.json({ success: false, error: error.message }, 500);
  }
}
