import type { ReferenceSearchResult, SearchIntent } from "@skola/shared";

interface SearchConfig {
  crossrefMailto: string;
  openalexEmail: string;
}

// ---------------------------------------------------------------------------
// Crossref
// ---------------------------------------------------------------------------

interface CrossrefAuthor {
  given?: string;
  family?: string;
}

interface CrossrefItem {
  DOI?: string;
  title?: string[];
  author?: CrossrefAuthor[];
  published?: { "date-parts"?: number[][] };
  "container-title"?: string[];
  abstract?: string;
}

interface CrossrefResponse {
  message?: {
    items?: CrossrefItem[];
  };
}

function crossrefAuthorName(author: CrossrefAuthor): string {
  const parts = [author.family, author.given].filter(Boolean);
  return parts.join(", ");
}

async function searchCrossref(
  query: string,
  maxResults: number,
  mailto: string
): Promise<ReferenceSearchResult[]> {
  try {
    const url = new URL("https://api.crossref.org/works");
    url.searchParams.set("query", query);
    url.searchParams.set("rows", String(maxResults));
    url.searchParams.set(
      "select",
      "DOI,title,author,published,container-title,abstract"
    );

    const userAgent = mailto ? `Skola/0.1 (mailto:${mailto})` : "Skola/0.1";

    const response = await fetch(url.toString(), {
      headers: { "User-Agent": userAgent }
    });

    if (!response.ok) return [];

    const data = (await response.json()) as CrossrefResponse;
    const items = data.message?.items ?? [];

    return items.map((item): ReferenceSearchResult => {
      const doi = item.DOI ?? "";
      const title = Array.isArray(item.title) ? (item.title[0] ?? "Untitled") : "Untitled";
      const authors = (item.author ?? []).map(crossrefAuthorName).filter(Boolean);
      const yearParts = item.published?.["date-parts"]?.[0];
      const year = yearParts ? (yearParts[0] ?? 0) : 0;
      const venue = Array.isArray(item["container-title"]) ? (item["container-title"][0] ?? "") : "";
      const abstractSnippet = item.abstract
        ? item.abstract.replace(/<[^>]+>/g, "").slice(0, 200)
        : undefined;

      return {
        id: `crossref-${doi}`,
        title,
        authors,
        year,
        venue,
        doi,
        abstractSnippet,
        rationale: "Retrieved from Crossref",
        openAccess: false
      };
    });
  } catch {
    return [];
  }
}

// ---------------------------------------------------------------------------
// OpenAlex
// ---------------------------------------------------------------------------

interface OpenAlexAuthorshipAuthor {
  display_name?: string;
}

interface OpenAlexAuthorship {
  author?: OpenAlexAuthorshipAuthor;
}

interface OpenAlexWork {
  id?: string;
  doi?: string;
  title?: string;
  authorships?: OpenAlexAuthorship[];
  publication_year?: number;
  primary_location?: { source?: { display_name?: string } };
  abstract_inverted_index?: Record<string, number[]>;
  open_access?: { is_oa?: boolean };
}

interface OpenAlexResponse {
  results?: OpenAlexWork[];
}

async function searchOpenAlex(
  query: string,
  perPage: number,
  email: string
): Promise<ReferenceSearchResult[]> {
  try {
    const url = new URL("https://api.openalex.org/works");
    url.searchParams.set("search", query);
    url.searchParams.set("per-page", String(perPage));
    if (email) url.searchParams.set("mailto", email);

    const response = await fetch(url.toString(), {
      headers: { "User-Agent": "Skola/0.1" }
    });

    if (!response.ok) return [];

    const data = (await response.json()) as OpenAlexResponse;
    const results = data.results ?? [];

    return results.map((work): ReferenceSearchResult => {
      const doiRaw = work.doi ?? "";
      const doi = doiRaw.replace("https://doi.org/", "");
      const id = work.id
        ? `openalex-${work.id.split("/").pop()}`
        : `openalex-${Math.random()}`;
      const title = work.title ?? "Untitled";
      const authors = (work.authorships ?? [])
        .map((a) => a.author?.display_name ?? "")
        .filter(Boolean);
      const year = work.publication_year ?? 0;
      const venue = work.primary_location?.source?.display_name ?? "";
      const abstractSnippet = work.abstract_inverted_index
        ? "[Abstract available]"
        : undefined;
      const openAccess = work.open_access?.is_oa ?? false;

      return {
        id,
        title,
        authors,
        year,
        venue,
        doi: doi || undefined,
        abstractSnippet,
        rationale: "Retrieved from OpenAlex",
        openAccess
      };
    });
  } catch {
    return [];
  }
}

// ---------------------------------------------------------------------------
// Semantic Scholar
// ---------------------------------------------------------------------------

interface S2Author {
  name?: string;
}

interface S2Paper {
  paperId?: string;
  title?: string;
  authors?: S2Author[];
  year?: number;
  venue?: string;
  externalIds?: { DOI?: string };
  abstract?: string;
}

interface S2Response {
  data?: S2Paper[];
}

async function searchSemanticScholar(
  query: string,
  limit: number
): Promise<ReferenceSearchResult[]> {
  try {
    const url = new URL("https://api.semanticscholar.org/graph/v1/paper/search");
    url.searchParams.set("query", query);
    url.searchParams.set("limit", String(limit));
    url.searchParams.set(
      "fields",
      "title,authors,year,venue,externalIds,abstract,citationCount"
    );

    const response = await fetch(url.toString(), {
      headers: { "User-Agent": "Skola/0.1" }
    });

    if (!response.ok) return [];

    const data = (await response.json()) as S2Response;
    const papers = data.data ?? [];

    return papers.map((paper): ReferenceSearchResult => {
      const doi = paper.externalIds?.DOI ?? "";
      const id = paper.paperId ? `s2-${paper.paperId}` : `s2-${Math.random()}`;
      const title = paper.title ?? "Untitled";
      const authors = (paper.authors ?? []).map((a) => a.name ?? "").filter(Boolean);
      const year = paper.year ?? 0;
      const venue = paper.venue ?? "";
      const abstractSnippet = paper.abstract ? paper.abstract.slice(0, 200) : undefined;

      return {
        id,
        title,
        authors,
        year,
        venue,
        doi: doi || undefined,
        abstractSnippet,
        rationale: "Retrieved from Semantic Scholar",
        openAccess: false
      };
    });
  } catch {
    return [];
  }
}

// ---------------------------------------------------------------------------
// Dedup and intent-based sorting
// ---------------------------------------------------------------------------

function deduplicateByDoi(results: ReferenceSearchResult[]): ReferenceSearchResult[] {
  const seen = new Set<string>();
  const deduped: ReferenceSearchResult[] = [];

  for (const r of results) {
    const key = r.doi ? r.doi.toLowerCase() : r.id;
    if (!seen.has(key)) {
      seen.add(key);
      deduped.push(r);
    }
  }

  return deduped;
}

function applyIntentSorting(
  results: ReferenceSearchResult[],
  intent: SearchIntent,
  maxResults: number
): ReferenceSearchResult[] {
  const currentYear = new Date().getUTCFullYear();

  switch (intent) {
    case "recent": {
      const filtered = results.length > maxResults
        ? results.filter((r) => (r.year ?? 0) >= currentYear - 3)
        : results;
      return [...(filtered.length ? filtered : results)].sort(
        (a, b) => (b.year || 0) - (a.year || 0)
      );
    }
    case "foundational":
      return [...results].sort((a, b) => (a.year || 0) - (b.year || 0));
    case "conflicting":
      return results.map((r) => ({
        ...r,
        rationale: `[Potentially contrasting] ${r.rationale}`
      }));
    case "support":
    default:
      return results;
  }
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

export async function searchAcademicSources(
  claimText: string,
  intent: SearchIntent,
  maxResults: number,
  config: SearchConfig
): Promise<ReferenceSearchResult[]> {
  const perSource = Math.max(maxResults, 5);

  const [crossrefResult, openAlexResult, s2Result] = await Promise.allSettled([
    searchCrossref(claimText, perSource, config.crossrefMailto || "support@skola.app"),
    searchOpenAlex(claimText, perSource, config.openalexEmail || "support@skola.app"),
    searchSemanticScholar(claimText, perSource)
  ]);

  const all: ReferenceSearchResult[] = [
    ...(crossrefResult.status === "fulfilled" ? crossrefResult.value : []),
    ...(openAlexResult.status === "fulfilled" ? openAlexResult.value : []),
    ...(s2Result.status === "fulfilled" ? s2Result.value : [])
  ];

  const deduped = deduplicateByDoi(all);
  const sorted = applyIntentSorting(deduped, intent, maxResults);

  return sorted.slice(0, maxResults);
}
