const { decodeEntities, sanitizeText } = require("../html_extractor");
const { decodeBody } = require("../page_fetcher");
const { evaluateNetworkTarget } = require("../url_policy");

class GenericHtmlSearchAdapter {
  constructor(options = {}) {
    this.fetcher = options.fetcher;
    this.id = "duckduckgo_html";
  }

  async search(query, options = {}) {
    const endpoint = new URL("https://html.duckduckgo.com/html/");
    endpoint.searchParams.set("q", query);
    if (options.freshness) endpoint.searchParams.set("df", freshnessValue(options.freshness));
    const policy = await evaluateNetworkTarget(endpoint.href, { resolveHost: this.fetcher.resolveHost });
    if (!policy.allowed) throw new Error(`Search source blocked: ${policy.reason}.`);
    const response = await this.fetcher.request(policy.url, options.settings, {
      networkOnly: true,
      timeoutMs: options.settings.search_timeout_ms,
      maxBytes: Math.min(options.settings.max_fetch_bytes, 2 * 1024 * 1024),
      accept: "text/html,application/xhtml+xml"
    });
    if (!response.ok) throw new Error(`DuckDuckGo search failed (${response.status}).`);
    const html = decodeBody(
      response.body,
      response.headers.get("content-encoding"),
      Math.min(options.settings.max_fetch_bytes, 2 * 1024 * 1024)
    ).toString("utf8");
    return parseDuckDuckGoHtml(html).slice(0, Math.max(options.settings.max_results * 2, 10));
  }
}

function parseDuckDuckGoHtml(html) {
  const rows = [];
  const expression = /<a\b[^>]*class\s*=\s*["'][^"']*(?:result__a|result-link)[^"']*["'][^>]*href\s*=\s*["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi;
  const matches = [...String(html || "").matchAll(expression)];
  for (let index = 0; index < matches.length; index += 1) {
    const match = matches[index];
    const nextOffset = matches[index + 1]?.index || Math.min(String(html).length, match.index + 5000);
    const vicinity = String(html).slice(match.index + match[0].length, nextOffset);
    const snippet = vicinity.match(
      /<(?:a|div|td)\b[^>]*class\s*=\s*["'][^"']*(?:result__snippet|result-snippet)[^"']*["'][^>]*>([\s\S]*?)<\/(?:a|div|td)>/i
    )?.[1] || "";
    const url = decodeResultUrl(decodeEntities(match[1]));
    if (!url) continue;
    rows.push({
      title: sanitizeText(match[2], 240),
      url,
      snippet: sanitizeText(snippet, 900),
      date: extractDate(snippet),
      rank: rows.length + 1,
      source: "duckduckgo_html",
      raw_source_id: `ddg-${rows.length + 1}`
    });
  }
  return rows;
}

function decodeResultUrl(value) {
  try {
    const resolved = new URL(value, "https://html.duckduckgo.com/");
    const redirected = resolved.searchParams.get("uddg");
    const target = redirected ? decodeURIComponent(redirected) : resolved.href;
    const parsed = new URL(target);
    return ["http:", "https:"].includes(parsed.protocol) ? parsed.href : null;
  } catch {
    return null;
  }
}

function freshnessValue(value) {
  const normalized = String(value || "").toLowerCase();
  if (/day|24h/.test(normalized)) return "d";
  if (/week|7d/.test(normalized)) return "w";
  if (/month|30d/.test(normalized)) return "m";
  if (/year|365d/.test(normalized)) return "y";
  return "";
}

function extractDate(value) {
  const match = sanitizeText(value, 300).match(/\b(20\d{2}-\d{2}-\d{2}|[A-Z][a-z]{2,8}\s+\d{1,2},\s+20\d{2})\b/);
  if (!match) return null;
  const date = new Date(match[1]);
  return Number.isNaN(date.getTime()) ? null : date.toISOString();
}

module.exports = {
  GenericHtmlSearchAdapter,
  decodeResultUrl,
  parseDuckDuckGoHtml
};
