import { fetchWithTimeout, warnDev } from '../providerUtils';
import type {
  PlantReferenceLookupInput,
  PlantReferenceProvider,
  PlantReferenceResult,
} from './types';

type WikipediaSummaryResponse = {
  type?: unknown;
  title?: unknown;
  description?: unknown;
  extract?: unknown;
  content_urls?: {
    desktop?: {
      page?: unknown;
    };
  };
};

type WikipediaSearchMatch = {
  title?: unknown;
  snippet?: unknown;
};

type WikipediaSearchResponse = {
  query?: {
    search?: unknown;
  };
};

const wikipediaSummaryEndpoint = 'https://en.wikipedia.org/api/rest_v1/page/summary/';
const wikipediaSearchEndpoint = 'https://en.wikipedia.org/w/api.php';
const requestTimeoutMs = 8_000;
const maxLookupLength = 180;
const plantSnippetPattern = /\bplant\b|\bspecies\b|\bgenus\b|\bfamily\b|\bflower\b|\bleaf\b|\btree\b|\bshrub\b|\bherb\b|\bvine\b/i;

function isRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}

function readString(value: unknown): string | undefined {
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
}

function normalizeLookupText(value?: string): string {
  if (typeof value !== 'string') return '';

  return value
    .replace(/[\u0000-\u001F\u007F]/g, ' ')
    .replace(/\s+/g, ' ')
    .trim()
    .slice(0, maxLookupLength);
}

function uniqueQueries(input: PlantReferenceLookupInput): string[] {
  const seen = new Set<string>();
  const queries: string[] = [];

  for (const value of [input.scientificName, input.commonName, input.displayName]) {
    const query = normalizeLookupText(value);
    const key = query.toLocaleLowerCase('en-US');
    if (!query || seen.has(key)) continue;
    seen.add(key);
    queries.push(query);
  }

  return queries;
}

function decodeNumericEntity(entity: string, code: string, radix: number): string {
  const codePoint = Number.parseInt(code, radix);
  return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 0x10_FFFF
    ? String.fromCodePoint(codePoint)
    : entity;
}

function decodeHtmlEntities(value: string): string {
  return value
    .replace(/&quot;/gi, '"')
    .replace(/&#0*39;|&apos;/gi, "'")
    .replace(/&amp;/gi, '&')
    .replace(/&lt;/gi, '<')
    .replace(/&gt;/gi, '>')
    .replace(/&#(\d+);/g, (entity: string, code: string) => decodeNumericEntity(entity, code, 10))
    .replace(/&#x([\da-f]+);/gi, (entity: string, code: string) => decodeNumericEntity(entity, code, 16));
}

function stripHtml(value: unknown): string {
  const text = readString(value);
  if (!text) return '';
  return decodeHtmlEntities(text.replace(/<[^>]*>/g, '')).trim();
}

function isUsableSummary(summary: WikipediaSummaryResponse): boolean {
  const title = readString(summary.title);
  const type = readString(summary.type)?.toLowerCase();

  if (!title) return false;
  if (type === 'disambiguation') return false;
  return !/\bdisambiguation\b/i.test(title);
}

async function requestJson(url: string): Promise<unknown | null> {
  try {
    const response = await fetchWithTimeout(
      url,
      {
        method: 'GET',
        headers: {
          Accept: 'application/json',
        },
      },
      requestTimeoutMs,
    );

    if (!response.ok) return null;
    return await response.json() as unknown;
  } catch (error) {
    warnDev('[PlantReference] Wikipedia request failed', {
      message: error instanceof Error ? error.message : String(error),
    });
    return null;
  }
}

async function fetchSummary(query: string): Promise<PlantReferenceResult | null> {
  const payload = await requestJson(`${wikipediaSummaryEndpoint}${encodeURIComponent(query)}`);
  if (!isRecord(payload)) return null;

  const summary = payload as WikipediaSummaryResponse;
  if (!isUsableSummary(summary)) return null;

  const title = readString(summary.title) || query;
  const url = readString(summary.content_urls?.desktop?.page)
    || `https://en.wikipedia.org/wiki/${encodeURIComponent(query.replace(/\s+/g, '_'))}`;
  const description = readString(summary.description) || readString(summary.extract);

  return {
    title,
    url,
    ...(description ? { description } : {}),
    source: 'Wikipedia',
  };
}

function readSearchMatches(payload: unknown): WikipediaSearchMatch[] {
  if (!isRecord(payload)) return [];

  const search = (payload as WikipediaSearchResponse).query?.search;
  if (!Array.isArray(search)) return [];
  return search.filter(isRecord) as WikipediaSearchMatch[];
}

async function searchWikipedia(query: string): Promise<string | null> {
  const params = new URLSearchParams({
    action: 'query',
    list: 'search',
    srsearch: query,
    srnamespace: '0',
    srprop: 'snippet',
    format: 'json',
    formatversion: '2',
    origin: '*',
    utf8: '1',
    srlimit: '5',
  });
  const payload = await requestJson(`${wikipediaSearchEndpoint}?${params.toString()}`);
  const matches = readSearchMatches(payload);
  const normalizedQuery = query.toLocaleLowerCase('en-US');

  const exactMatch = matches.find(match => (
    normalizeLookupText(readString(match.title)).toLocaleLowerCase('en-US') === normalizedQuery
  ));
  if (exactMatch) return readString(exactMatch.title) || null;

  const firstPlantLikeMatch = matches.find(match => plantSnippetPattern.test(stripHtml(match.snippet)));
  return readString(firstPlantLikeMatch?.title) || null;
}

export const wikipediaProvider: PlantReferenceProvider = {
  async lookupPlantReference(input) {
    const queries = uniqueQueries(input);

    for (const query of queries) {
      const exactResult = await fetchSummary(query);
      if (exactResult) return exactResult;
    }

    for (const query of queries) {
      const searchTitle = await searchWikipedia(query);
      if (!searchTitle) continue;

      const searchResult = await fetchSummary(searchTitle);
      if (searchResult) return searchResult;
    }

    return null;
  },
};
