// EL RESPALDO PROPIO DE LA IMPRENTA (ver wrangler.toml para el porque).
//
// Cada corrida: volcado del esquema fiscal -> cifrado AES-256-GCM aca mismo ->
// caja cerrada al deposito privado. El objeto es autocontenido (los 12 bytes
// del iv van adelante del cifrado), asi que para restaurar solo hacen falta el
// archivo y la clave, que vive en el Worker y APUNTADA en papel donde Gilberto.
//
// Nada de borrar respaldos viejos: un volcado pesa kilobytes y el historial
// completo es parte de la gracia (se puede volver a cualquier dia).

const hexABytes = (hex) => new Uint8Array(hex.match(/.{2}/g).map((b) => parseInt(b, 16)));

async function llaveDeCifrado(env) {
  const hex = (env.CLAVE_CIFRADO ?? '').trim();
  if (!/^[0-9a-f]{64}$/i.test(hex)) throw new Error('CLAVE_CIFRADO debe ser 64 caracteres hexadecimales');
  return crypto.subtle.importKey('raw', hexABytes(hex), { name: 'AES-GCM' }, false, ['encrypt']);
}

async function respaldar(env) {
  const r = await fetch(`${env.SUPABASE_URL}/rest/v1/rpc/fn_respaldo_imprenta`, {
    method: 'POST',
    headers: {
      apikey: env.SUPABASE_SERVICE_KEY,
      Authorization: `Bearer ${env.SUPABASE_SERVICE_KEY}`,
      'Content-Type': 'application/json',
    },
    body: '{}',
  });
  if (!r.ok) throw new Error(`La base respondio ${r.status}: ${(await r.text()).slice(0, 200)}`);
  const claro = new TextEncoder().encode(await r.text());

  const iv = crypto.getRandomValues(new Uint8Array(12));
  const llave = await llaveDeCifrado(env);
  const cifrado = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, llave, claro));
  const caja = new Uint8Array(iv.length + cifrado.length);
  caja.set(iv, 0);
  caja.set(cifrado, iv.length);

  const cuando = new Date().toISOString().replace(/:/g, '-').slice(0, 16);
  const objeto = `fiscal/${cuando}.json.enc`;
  await env.DEPOSITO.put(objeto, caja, {
    customMetadata: { version: '1', bytes_claro: String(claro.length) },
  });
  // La ficha del ultimo respaldo, en claro y SIN datos: solo para comprobar
  // que la vuelta corrio, sin bajar ni abrir ninguna caja.
  const ficha = { objeto, cuando: new Date().toISOString(), bytes_claro: claro.length, bytes_caja: caja.length };
  await env.DEPOSITO.put('fiscal/ultimo.json', JSON.stringify(ficha, null, 2));
  return ficha;
}

export default {
  async scheduled(_evento, env, ctx) {
    ctx.waitUntil(respaldar(env));
  },

  // El disparo y la comprobacion manual, con su secreto. No hay GET publico:
  // hasta la ficha (que no trae datos) exige la llave.
  async fetch(req, env) {
    const secreto = req.headers.get('x-respaldo-secreto') ?? '';
    if (!env.SECRETO_DISPARO || secreto !== env.SECRETO_DISPARO) {
      return new Response(JSON.stringify({ problema: 'Sin permiso' }), { status: 401 });
    }
    const url = new URL(req.url);
    try {
      if (req.method === 'POST' && url.pathname === '/correr') {
        const ficha = await respaldar(env);
        return Response.json({ ok: true, ...ficha });
      }
      if (req.method === 'GET' && url.pathname === '/ultimo') {
        const obj = await env.DEPOSITO.get('fiscal/ultimo.json');
        if (!obj) return Response.json({ ok: false, problema: 'Todavia no hay ningun respaldo' }, { status: 404 });
        return new Response(await obj.text(), { headers: { 'content-type': 'application/json' } });
      }
      if (req.method === 'GET' && url.pathname === '/bajar') {
        // Baja una caja cerrada puntual (para la prueba de restauracion).
        const objeto = url.searchParams.get('objeto') ?? '';
        if (!/^fiscal\/[0-9T:-]+\.json\.enc$/.test(objeto)) {
          return Response.json({ ok: false, problema: 'Nombre de objeto invalido' }, { status: 400 });
        }
        const obj = await env.DEPOSITO.get(objeto);
        if (!obj) return Response.json({ ok: false, problema: 'No existe' }, { status: 404 });
        return new Response(obj.body, { headers: { 'content-type': 'application/octet-stream' } });
      }
      return Response.json({ ok: false, problema: 'Ruta desconocida' }, { status: 404 });
    } catch (e) {
      return Response.json({ ok: false, problema: String(e?.message ?? e).slice(0, 300) }, { status: 500 });
    }
  },
};
