#!/usr/bin/env node
// Bot verificador de /prensa.
//
// Lee cada fuente de src/data/menciones.json y comprueba, contra la fuente real:
//   check "texto": la página responde y su contenido nombra a "Carrasco".
//   check "video": el video sigue en línea (oEmbed de YouTube) y en el canal esperado.
// Escribe src/data/verificacion.json. El sitio sólo publica lo que pasó la última
// verificación: si una fuente cae o deja de nombrarlo, sale sola de la página.
//
// Tolerancia: una fuente que ya estaba verificada y hoy no se puede confirmar
// (Instagram bloquea IPs de datacenter, un sitio cae un rato) se mantiene hasta
// GRACE_DAYS desde su última confirmación, y el log lo dice. Pasado eso, sale.
// Si falla más de la mitad (red caída, no fuentes caídas) no pisa el archivo
// anterior, para no vaciar la página por un problema de conectividad.
//
// Uso: npm run verificar
// (RedSalud manda cabeceras enormes: el script necesita --max-http-header-size, ya puesto en package.json)

import { readFile, writeFile } from 'node:fs/promises';

const GRACE_DAYS = 30;

const ROOT = new URL('../', import.meta.url);
const SOURCES = new URL('src/data/menciones.json', ROOT);
const OUT = new URL('src/data/verificacion.json', ROOT);

const BROWSER_UA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126 Safari/537.36';
// Instagram y Facebook sólo entregan el texto del post a crawlers de vista previa.
const SOCIAL_UA = 'facebookexternalhit/1.1';
const NAME = /Carrasco/i;

const decode = (s) =>
  s
    .replace(/&#x([0-9a-f]+);/gi, (_, h) => String.fromCodePoint(parseInt(h, 16)))
    .replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)))
    .replace(/&[a-z]+;/gi, ' ');

async function get(url, ua) {
  const res = await fetch(url, {
    headers: { 'User-Agent': ua, 'Accept-Language': 'es-CL,es;q=0.9' },
    redirect: 'follow',
    signal: AbortSignal.timeout(25_000),
  });
  return { status: res.status, body: await res.text() };
}

async function checkTexto(src) {
  const social = /instagram\.com|facebook\.com/.test(src.url);
  const { status, body } = await get(src.url, social ? SOCIAL_UA : BROWSER_UA);
  const host = new URL(src.url).hostname.replace(/^www\./, '');
  if (status >= 400) return { ok: false, http: status, detail: `${host} respondió ${status}` };
  const text = decode(body.replace(/<script[\s\S]*?<\/script>|<style[\s\S]*?<\/style>/gi, ' '));
  const found = NAME.test(text);
  return {
    ok: found,
    http: status,
    detail: found ? `${host} · "Carrasco" encontrado en el texto` : `${host} · la fuente ya no lo nombra`,
  };
}

async function checkVideo(src) {
  const oembed = `https://www.youtube.com/oembed?format=json&url=${encodeURIComponent(src.url)}`;
  const { status, body } = await get(oembed, BROWSER_UA);
  if (status >= 400) return { ok: false, http: status, detail: `youtube · video no disponible (${status})` };
  const data = JSON.parse(body);
  const channelOk = !src.channel || data.author_name.includes(src.channel);
  return {
    ok: channelOk,
    http: status,
    detail: channelOk
      ? `youtube · en línea en "${data.author_name}"${src.time ? ` · cuña al ${src.time}` : ''}`
      : `youtube · canal inesperado: ${data.author_name}`,
  };
}

const sources = JSON.parse(await readFile(SOURCES, 'utf8'));
const previous = await readFile(OUT, 'utf8').then(JSON.parse).catch(() => ({ results: {} }));
const checkedAt = new Date().toISOString();
const results = {};
let errors = 0;

for (const src of sources) {
  try {
    const r = src.check === 'video' ? await checkVideo(src) : await checkTexto(src);
    results[src.id] = { ...r, check: src.check };
  } catch (err) {
    errors++;
    results[src.id] = { ok: false, http: 0, check: src.check, detail: `sin respuesta (${err.name})` };
  }
  const prev = previous.results?.[src.id];
  if (results[src.id].ok) {
    results[src.id].lastOk = checkedAt;
  } else if (prev?.lastOk) {
    const days = (Date.parse(checkedAt) - Date.parse(prev.lastOk)) / 86_400_000;
    if (days <= GRACE_DAYS) {
      results[src.id] = {
        ...results[src.id],
        ok: true,
        lastOk: prev.lastOk,
        detail: `${results[src.id].detail} · se mantiene: confirmado el ${prev.lastOk.slice(0, 10)} (tolerancia ${GRACE_DAYS} días)`,
      };
    }
  }
  const r = results[src.id];
  console.log(`${r.ok ? '✓' : '✗'} ${src.id} — ${r.detail}`);
}

const published = Object.values(results).filter((r) => r.ok).length;
console.log(`\n${published}/${sources.length} fuentes verificadas`);

if (errors > sources.length / 2) {
  console.error('Más de la mitad sin respuesta: parece la red, no las fuentes. No se actualiza verificacion.json.');
  process.exit(0);
}

await writeFile(OUT, JSON.stringify({ checkedAt, total: sources.length, published, results }, null, 2) + '\n');
