#!/usr/bin/env python3
"""
End-to-end literature search: query arXiv + Semantic Scholar + Crossref +
OpenAlex, merge/dedupe candidates, independently verify each one, and emit
both a human-readable report and BibTeX for the entries that passed
verification.

This is the one script Claude should actually call for a normal literature
search -- the individual search_*.py / verify_citation.py scripts exist
mainly as building blocks it can reuse for one-off / follow-up lookups.

CLI usage:
    python3 literature_search.py "UAV magnetic compensation Tolles-Lawson" \\
        --max-per-source 8 --bib-out refs.bib

Output: prints a JSON report to stdout (one entry per merged candidate,
with its verdict), and if --bib-out is given, writes BibTeX for every
"verified" entry to that file (never for "suspect" or "unverified" ones).
"""
import sys
import os
import json
import argparse
import difflib
import re

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from search_arxiv import search_arxiv
from search_semantic_scholar import search_s2
from search_crossref import search_crossref
from search_openalex import search_openalex
from verify_citation import verify


def _similar(a, b, threshold=0.88):
    if not a or not b:
        return False
    return difflib.SequenceMatcher(None, a.lower().strip(), b.lower().strip()).ratio() >= threshold


def merge_candidates(all_results):
    """Dedupe candidates that are the same paper found via multiple sources,
    merging their metadata (preferring whichever source has an identifier)."""
    merged = []
    for item in all_results:
        placed = False
        for m in merged:
            if _similar(item.get("title"), m.get("title")):
                # merge: fill in any missing fields, keep track of all sources
                for key in ("doi", "arxiv_id", "abstract", "venue", "year", "citation_count", "pdf_url"):
                    if not m.get(key) and item.get(key):
                        m[key] = item[key]
                m["sources"] = sorted(set(m.get("sources", [m.get("source")]) + [item.get("source")]))
                placed = True
                break
        if not placed:
            item = dict(item)
            item["sources"] = [item.get("source")]
            merged.append(item)
    return merged


def make_bibtex_key(candidate, used_keys):
    authors = candidate.get("authors") or []
    surname = "unknown"
    if authors:
        first_author = authors[0]
        surname = first_author.strip().split()[-1].lower()
        surname = re.sub(r"[^a-z]", "", surname) or "unknown"
    year = str(candidate.get("year") or "nd")
    base = f"{surname}{year}"
    key = base
    suffix = ord("a")
    while key in used_keys:
        key = f"{base}{chr(suffix)}"
        suffix += 1
    used_keys.add(key)
    return key


def to_bibtex(candidate, key):
    authors = candidate.get("authors") or []
    author_str = " and ".join(authors) if authors else "Unknown"
    title = candidate.get("title") or ""
    year = candidate.get("year") or ""
    venue = candidate.get("venue") or ""
    doi = candidate.get("doi") or ""
    arxiv_id = candidate.get("arxiv_id") or ""

    if arxiv_id and not venue:
        entry_type = "misc"
        fields = [
            ("author", author_str),
            ("title", title),
            ("year", str(year)),
            ("eprint", arxiv_id),
            ("archivePrefix", "arXiv"),
        ]
    else:
        entry_type = "article"
        fields = [
            ("author", author_str),
            ("title", title),
            ("journal", venue),
            ("year", str(year)),
        ]
        if doi:
            fields.append(("doi", doi))

    lines = [f"@{entry_type}{{{key},"]
    for k, v in fields:
        if v:
            lines.append(f"  {k} = {{{v}}},")
    lines.append("}")
    return "\n".join(lines)


def run(query, max_per_source=8):
    all_results = []
    errors = {}
    sources = (
        ("arxiv", search_arxiv),
        ("semantic_scholar", search_s2),
        ("crossref", search_crossref),
        ("openalex", search_openalex),
    )
    for name, fn in sources:
        try:
            all_results.extend(fn(query, max_per_source))
        except Exception as e:
            errors[name] = str(e)

    merged = merge_candidates(all_results)

    used_keys = set()
    for cand in merged:
        result = verify(title=cand.get("title"), arxiv_id=cand.get("arxiv_id"), doi=cand.get("doi"))
        cand["verdict"] = result["verdict"]
        cand["verification_checks"] = result["checks"]
        if result["verdict"] == "verified":
            cand["bibtex_key"] = make_bibtex_key(cand, used_keys)

    return {"query": query, "search_errors": errors, "candidates": merged}


if __name__ == "__main__":
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("query")
    ap.add_argument("--max-per-source", type=int, default=8)
    ap.add_argument("--bib-out", default=None, help="path to write BibTeX for verified entries")
    args = ap.parse_args()

    try:
        report = run(args.query, args.max_per_source)
    except Exception as e:
        print(json.dumps({"error": str(e)}, ensure_ascii=False))
        sys.exit(1)

    print(json.dumps(report, ensure_ascii=False, indent=2))

    if args.bib_out:
        verified = [c for c in report["candidates"] if c["verdict"] == "verified"]
        with open(args.bib_out, "w", encoding="utf-8") as f:
            for cand in verified:
                f.write(to_bibtex(cand, cand["bibtex_key"]))
                f.write("\n\n")
        sys.stderr.write(f"Wrote {len(verified)} verified BibTeX entries to {args.bib_out}\n")
