#!/usr/bin/env python3
"""Resolve each taxon name to a real English Wikipedia article, verified via the API.

Never construct a URL and hope. For each name we ask the API directly, follow
redirects, reject missing pages and disambiguation pages, and fall back from a
subspecies trinomial to its parent binomial -- recording which happened so the
UI can be honest about it.
"""
import csv, json, time, urllib.parse, urllib.request
import os
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

API = "https://en.wikipedia.org/w/api.php"
UA = "SihekMoatFigure/1.0 (research figure; contact via local use)"

# GenBank nomenclature lags current taxonomy; these are the synonymies the
# project report already flagged as unambiguous.
SYNONYM = {
    "Halcyon ruficollaris": "Todiramphus ruficollaris",
}


def api(titles):
    q = urllib.parse.urlencode({
        "action": "query", "format": "json", "formatversion": "2",
        "titles": "|".join(titles), "redirects": "1",
        "prop": "pageprops", "ppprop": "disambiguation",
    })
    req = urllib.request.Request(f"{API}?{q}", headers={"User-Agent": UA})
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.load(r)


def resolve(names):
    """titles -> resolved article title (or None). Follows normalisation+redirects."""
    out = {}
    for i in range(0, len(names), 40):
        batch = names[i:i + 40]
        d = api(batch).get("query", {})
        alias = {}
        for k in ("normalized", "redirects"):
            for m in d.get(k, []):
                alias[m["from"]] = m["to"]
        status = {}
        for p in d.get("pages", []):
            ok = not p.get("missing") and "disambiguation" not in (p.get("pageprops") or {})
            status[p["title"]] = p["title"] if ok else None
        for n in batch:
            t = n
            for _ in range(4):                      # walk the alias chain
                t = alias.get(t, t)
            out[n] = status.get(t)
        time.sleep(0.2)
    return out


names = sorted({r["species"] for r in csv.DictReader(
    open(os.path.join(ROOT, "data", "taxa_accessions.csv")))})

# pass 1: the name as given (after applying known synonymies)
probe = {n: SYNONYM.get(n, n) for n in names}
first = resolve(sorted(set(probe.values())))

result = {}
fallback_needed = []
for n in names:
    hit = first.get(probe[n])
    if hit:
        result[n] = {"title": hit, "kind": "synonym" if probe[n] != n else "exact"}
    else:
        fallback_needed.append(n)

# pass 2: trinomials fall back to their parent binomial
parents = {}
for n in fallback_needed:
    w = n.split()
    if len(w) >= 3:
        parents[n] = SYNONYM.get(" ".join(w[:2]), " ".join(w[:2]))
second = resolve(sorted(set(parents.values()))) if parents else {}

unresolved = []
for n in fallback_needed:
    hit = second.get(parents.get(n))
    if hit:
        result[n] = {"title": hit, "kind": "parent"}
    else:
        unresolved.append(n)

for v in result.values():
    v["url"] = "https://en.wikipedia.org/wiki/" + urllib.parse.quote(v["title"].replace(" ", "_"))

json.dump(result, open(os.path.join(ROOT, "figures", "wiki_links.json"), "w"),
          indent=1, sort_keys=True)

kinds = {}
for v in result.values():
    kinds[v["kind"]] = kinds.get(v["kind"], 0) + 1
print(f"{len(names)} names -> {len(result)} linked, {len(unresolved)} unlinked")
print("  by kind:", kinds)
if unresolved:
    print("\nno article found:")
    for n in unresolved:
        print("   ", n)
print("\nsample of parent-article fallbacks:")
for n, v in sorted(result.items()):
    if v["kind"] == "parent":
        print(f"    {n}  ->  {v['title']}")
