from __future__ import annotations

import argparse
import csv
import json
import sys
import time
from pathlib import Path
from urllib.parse import urlencode
from urllib.request import Request, build_opener


ROOT_DIR = Path(__file__).resolve().parents[1]
DEFAULT_SOURCE_CSV = ROOT_DIR / "scripts" / "data" / "latin_fallback_presets.csv"
DEFAULT_OVERRIDE_PATH = ROOT_DIR / "scripts" / "data" / "plant_name_overrides.json"
BASE_URL = "https://species.nibr.go.kr"
INFRASPECIFIC_MARKERS = {"subsp.", "subsp", "ssp.", "ssp", "var.", "var", "f.", "f", "forma", "cv.", "cv", "x", "×"}
HEADERS = {
    "User-Agent": "Mozilla/5.0",
    "Accept-Language": "ko,en-US;q=0.9,en;q=0.8",
    "Referer": f"{BASE_URL}/",
}


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Fill plant name overrides with official Korean names from NIBR exact species matches.")
    parser.add_argument("--source-csv", default=str(DEFAULT_SOURCE_CSV))
    parser.add_argument("--out", default=str(DEFAULT_OVERRIDE_PATH))
    parser.add_argument("--delay", type=float, default=0.12)
    parser.add_argument("--limit", type=int, default=0)
    return parser.parse_args()


def normalize(value: str) -> str:
    return " ".join((value or "").strip().split())


def is_exact_species_match(query: str, taxon_full_name: str) -> bool:
    query_tokens = normalize(query).split()
    full_tokens = normalize(taxon_full_name).split()
    if len(query_tokens) < 2 or len(full_tokens) < 2:
        return False
    if [token.lower() for token in full_tokens[:2]] != [token.lower() for token in query_tokens[:2]]:
        return False
    if len(full_tokens) == 2:
        return True
    third_token = full_tokens[2]
    third = third_token.lower()
    if third in INFRASPECIFIC_MARKERS:
        return False
    return not third_token[:1].islower()


def load_source_rows(path: Path) -> list[dict]:
    with path.open("r", encoding="utf-8-sig", newline="") as fp:
        return list(csv.DictReader(fp))


def load_overrides(path: Path) -> dict[str, dict[str, str]]:
    if not path.exists():
        return {}
    return json.loads(path.read_text(encoding="utf-8"))


def save_overrides(path: Path, payload: dict[str, dict[str, str]]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    ordered = {name: payload[name] for name in sorted(payload)}
    path.write_text(json.dumps(ordered, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")


def build_opener_with_session():
    opener = build_opener()
    opener.addheaders = list(HEADERS.items())
    opener.open(Request(f"{BASE_URL}/index.do", headers=HEADERS), timeout=30).read()
    return opener


def fetch_nibr_match(opener, scientific_name: str) -> dict | None:
    payload = urlencode(
        {
            "firstIndex": 0,
            "lastIndex": 1,
            "recordCountPerPage": 9,
            "searchField": scientific_name,
        }
    ).encode("utf-8")
    request = Request(
        f"{BASE_URL}/speciesSearch/searchEngineTotalAjax.do",
        data=payload,
        headers={**HEADERS, "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"},
    )
    with opener.open(request, timeout=30) as response:
        data = json.loads(response.read().decode("utf-8"))
    result_list = json.loads(data.get("resultList") or "[]")
    for item in result_list:
        if not item.get("TAXON_KNM"):
            continue
        if is_exact_species_match(scientific_name, item.get("TAXON_FULL_NM", "")):
            return item
    return None


def main() -> None:
    try:
        sys.stdout.reconfigure(encoding="utf-8")
    except Exception:
        pass

    args = parse_args()
    source_path = Path(args.source_csv)
    out_path = Path(args.out)

    source_rows = load_source_rows(source_path)
    if args.limit > 0:
        source_rows = source_rows[: args.limit]
    overrides = load_overrides(out_path)
    opener = build_opener_with_session()

    updated = 0
    already_present = 0
    no_match = 0
    sample = []

    for index, row in enumerate(source_rows, start=1):
        scientific_name = normalize(row.get("type_name", ""))
        if not scientific_name:
            continue

        existing = overrides.get(scientific_name, {})
        if normalize(existing.get("ko", "")) and normalize(existing.get("ko", "")) != scientific_name:
            already_present += 1
            continue

        match = fetch_nibr_match(opener, scientific_name)
        if not match:
            no_match += 1
        else:
            overrides[scientific_name] = {
                **existing,
                "ko": normalize(match.get("TAXON_KNM", "")),
                "source": "NIBR",
                "source_taxon_full_name": normalize(match.get("TAXON_FULL_NM", "")),
                "source_link": f"{BASE_URL}{match.get('LINKURL', '')}",
            }
            updated += 1
            if len(sample) < 20:
                sample.append(
                    {
                        "scientific_name": scientific_name,
                        "ko": overrides[scientific_name]["ko"],
                        "matched_taxon": overrides[scientific_name]["source_taxon_full_name"],
                    }
                )
            save_overrides(out_path, overrides)

        if index % 100 == 0:
            print(json.dumps({"progress": index, "updated": updated, "already_present": already_present, "no_match": no_match}, ensure_ascii=False))
        if args.delay > 0:
            time.sleep(args.delay)

    save_overrides(out_path, overrides)
    print(
        json.dumps(
            {
                "source_csv": str(source_path),
                "out": str(out_path),
                "processed": len(source_rows),
                "updated": updated,
                "already_present": already_present,
                "no_match": no_match,
                "sample": sample,
            },
            ensure_ascii=False,
            indent=2,
        )
    )


if __name__ == "__main__":
    main()
