import { getSearxngURL } from './config/serverRegistry';

const FALLBACK_INSTANCES = (
  process.env.SEARXNG_FALLBACK_URL
    ? process.env.SEARXNG_FALLBACK_URL.split(',').map((u) => u.trim())
    : ['https://searx.tiekoetter.com', 'https://search.sapti.me']
).filter(Boolean);

interface SearxngSearchOptions {
  categories?: string[];
  engines?: string[];
  language?: string;
  pageno?: number;
}

export interface SearxngSearchResult {
  title: string;
  url: string;
  img_src?: string;
  thumbnail_src?: string;
  thumbnail?: string;
  content?: string;
  author?: string;
  iframe_src?: string;
}

function buildSearchUrl(baseUrl: string, query: string, opts?: SearxngSearchOptions): string {
  const params = new URLSearchParams();
  params.append('format', 'json');
  params.append('q', query);
  if (opts) {
    Object.entries(opts).forEach(([key, value]) => {
      if (value == null) return;
      params.append(
        key,
        Array.isArray(value) ? value.join(',') : String(value),
      );
    });
  }
  const base = baseUrl.trim().replace(/\/$/, '');
  const prefix = /^https?:\/\//i.test(base) ? '' : 'http://';
  return `${prefix}${base}/search?${params.toString()}`;
}

export const searchSearxng = async (
  query: string,
  opts?: SearxngSearchOptions,
) => {
  const searxngURL = getSearxngURL();
  const candidates: string[] = [];

  if (searxngURL?.trim()) {
    let u = searxngURL.trim().replace(/\/$/, '');
    if (!/^https?:\/\//i.test(u)) u = `http://${u}`;
    candidates.push(u);
  }
  FALLBACK_INSTANCES.forEach((u) => {
    const trimmed = u.trim().replace(/\/$/, '');
    if (trimmed && !candidates.includes(trimmed)) candidates.push(trimmed);
  });

  let lastError: Error | null = null;

  const FETCH_TIMEOUT_MS = 15_000;

  for (const baseUrl of candidates) {
    try {
      const fullUrl = buildSearchUrl(baseUrl, query, opts);
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);

      const res = await fetch(fullUrl, { signal: controller.signal });
      clearTimeout(timeoutId);

      const text = await res.text();
      const ok = res.ok;
      const isJson =
        text.trim().startsWith('{') || text.trim().startsWith('[');

      if (res.status === 429) {
        const err = new Error(
          `SearXNG ${baseUrl}: лимит запросов (429). Укажите свой инстанс в настройках или попробуйте позже.`,
        );
        err.name = 'SearxngRateLimit';
        throw err;
      }

      if (!isJson) {
        throw new Error(
          `SearXNG ${baseUrl}: ответ не JSON (HTTP ${res.status}). Проверьте URL и поддержку format=json.`,
        );
      }

      const data = JSON.parse(text);
      const results: SearxngSearchResult[] = data.results ?? [];
      const suggestions: string[] = data.suggestions ?? [];

      if (!ok && results.length === 0) {
        const errMsg = text.slice(0, 200) || `HTTP ${res.status}`;
        throw new Error(`SearXNG ${baseUrl}: ${errMsg}`);
      }

      return { results, suggestions };
    } catch (err) {
      lastError =
        err instanceof Error ? err : new Error(String(err));
      const cause = (err as { cause?: { code?: string } })?.cause;
      const isAbort = lastError.name === 'AbortError';
      const isNetwork =
        isAbort ||
        lastError.message.includes('fetch failed') ||
        lastError.message.includes('Invalid URL') ||
        cause?.code === 'ECONNREFUSED' ||
        cause?.code === 'ECONNRESET';
      const isRateLimit =
        lastError.name === 'SearxngRateLimit' ||
        lastError.message.includes('429');
      const hasFallback = candidates.indexOf(baseUrl) < candidates.length - 1;
      if (hasFallback && (isNetwork || isRateLimit)) {
        continue;
      }
      if (isAbort) {
        throw new Error(
          `SearXNG ${baseUrl}: таймаут ${FETCH_TIMEOUT_MS / 1000}с. Проверьте подключение или используйте локальный инстанс.`,
        );
      }
      throw lastError;
    }
  }

  throw (
    lastError ??
    new Error(
      'SearXNG not configured. Set SEARXNG_API_URL or run Docker (includes SearXNG).',
    )
  );
};
