'use strict';

/* ════════════════════════════════════════════════════════════════
   SEGURIDAD
   ─ El único riesgo real aquí es SSRF indirecto: el usuario
     controla la cadena de búsqueda pero NO las URLs de destino
     (siempre son instancias hardcodeadas).
   ─ Mitigaciones aplicadas:
       · Sanitización: se eliminan caracteres de control y null bytes
       · Longitud máxima estricta (150 chars)
       · Los resultados se filtran antes de enviarse al cliente
         (solo se devuelven title/url/snippet/domain/engine)
       · Las URLs de resultados se validan con URL() antes de
         incluirlas en la respuesta
       · Timeout de 7 s para evitar que peticiones lentas bloqueen
       · Rate limit aplicado en app.js (8 req/min por IP)
   ════════════════════════════════════════════════════════════════ */

/* ── Instancias públicas SearXNG (fallback si no hay Google CSE) ── */
const SEARX_INSTANCES = [
  'https://searx.be',
  'https://priv.au',
  'https://search.mdosch.de',
  'https://searxng.site',
];

/* ── Sanitización de query ─────────────────────────────────── */
function sanitizeQuery(raw) {
  return raw
    .replace(/[\x00-\x1F\x7F]/g, '') // null bytes y caracteres de control
    .replace(/[<>]/g, '')              // evita inyección HTML en logs
    .trim()
    .slice(0, 150);
}

/* ── Valida que una URL de resultado sea http/https ─────────── */
function safeUrl(raw) {
  try {
    const u = new URL(raw);
    if (u.protocol !== 'http:' && u.protocol !== 'https:') return null;
    return u.href;
  } catch { return null; }
}

function domainOf(rawUrl) {
  try { return new URL(rawUrl).hostname.replace(/^www\./, ''); }
  catch { return ''; }
}

/* ── SearXNG ────────────────────────────────────────────────── */
async function searchSearX(instance, query) {
  const url = `${instance}/search?` + new URLSearchParams({
    q:          query,
    format:     'json',
    categories: 'general',
    language:   'es-ES',
  });

  const res = await fetch(url, {
    headers: {
      'User-Agent': 'resetea.net/1.0 (egosurfing privacy tool)',
      'Accept':     'application/json',
    },
    signal: AbortSignal.timeout(7000),
  });

  if (!res.ok) throw new Error(`HTTP ${res.status}`);

  // Limita tamaño de respuesta (previene memory exhaustion)
  const text = await res.text();
  if (text.length > 500_000) throw new Error('Respuesta demasiado grande');

  const data = JSON.parse(text);
  return (data.results || []).map(r => ({
    title:   String(r.title   || '').slice(0, 300),
    url:     String(r.url     || ''),
    snippet: String(r.content || '').slice(0, 500),
    engine:  String(r.engine  || 'web').slice(0, 50),
  }));
}

/* ── Google Custom Search ───────────────────────────────────── */
async function searchGoogle(query) {
  const url = 'https://www.googleapis.com/customsearch/v1?' + new URLSearchParams({
    key: process.env.GOOGLE_API_KEY,
    cx:  process.env.GOOGLE_CSE_ID,
    q:   query,
    num: 10,
    hl:  'es',
  });

  const res = await fetch(url, { signal: AbortSignal.timeout(7000) });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.error?.message || `HTTP ${res.status}`);
  }
  const text = await res.text();
  if (text.length > 500_000) throw new Error('Respuesta Google CSE demasiado grande');
  const data = JSON.parse(text);
  return (data.items || []).map(item => ({
    title:   String(item.title   || '').slice(0, 300),
    url:     String(item.link    || ''),
    snippet: String(item.snippet || '').slice(0, 500),
    engine:  'google',
  }));
}

const stats = require('../services/stats');

/* ── Handler principal ──────────────────────────────────────── */
module.exports = async (req, res) => {
  const raw = req.query.q;

  if (!raw || typeof raw !== 'string' || raw.trim().length < 2)
    return res.status(400).json({ error: 'Introduce al menos 2 caracteres.' });

  const q = sanitizeQuery(raw);

  if (q.length < 2)
    return res.status(400).json({ error: 'Búsqueda inválida.' });

  /* Si no tiene operadores especiales, forzar coincidencia exacta */
  const query = /[:"()]/.test(q) ? q : `"${q}"`;

  let raw_results = [];

  try {
    if (process.env.GOOGLE_API_KEY && process.env.GOOGLE_CSE_ID) {
      raw_results = await searchGoogle(query);
    } else {
      for (const inst of SEARX_INSTANCES) {
        try {
          raw_results = await searchSearX(inst, query);
          if (raw_results.length) break;
        } catch (e) {
          console.warn(`[egosearch] ${inst} falló:`, e.message);
        }
      }
    }
  } catch (e) {
    console.error('[egosearch] error:', e.message);
    return res.status(502).json({ error: 'El servicio de búsqueda no está disponible. Inténtalo de nuevo.' });
  }

  /* Filtrar y validar URLs de resultados antes de enviar al cliente */
  const results = raw_results
    .filter(r => safeUrl(r.url))        // solo URLs http/https válidas
    .slice(0, 5)
    .map(r => ({
      title:   r.title,
      url:     safeUrl(r.url),
      snippet: r.snippet,
      domain:  domainOf(r.url),
      engine:  r.engine,
    }));

  stats.record({ type: 'search' });
  res.json({ results, query, total: raw_results.length });
};
