"""Fetch the pinned upstream 5e-database JSON data.

Downloads the tagged source tarball from GitHub and extracts only its
``src/`` tree into ``data/`` (so ``data/2014/en/*.json`` etc.), replacing
whatever was vendored before. ``data/.upstream-tag`` records which tag the
tree came from and is written last, so an interrupted fetch never leaves a
tree that claims to be complete. The pin itself lives in pyproject.toml
under [tool.dnd5e-database-sqlite] — the single source of truth for which
upstream release our builds track.
"""

from __future__ import annotations

import argparse
import shutil
import tarfile
import tempfile
import tomllib
import urllib.request
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parent.parent
DATA_DIR = REPO_ROOT / "data"
MARKER_NAME = ".upstream-tag"


def vendored_tag(data_dir: Path = DATA_DIR) -> str | None:
    """The tag the vendored tree came from, or None if data/ is absent/partial."""
    marker = data_dir / MARKER_NAME
    if not marker.is_file():
        return None
    return marker.read_text(encoding="utf-8").strip()


def _load_pin() -> tuple[str, str] | tuple[None, None]:
    """The upstream pin from pyproject.toml, or (None, None) outside a repo
    checkout (an installed package doesn't ship pyproject.toml) - fetch/build
    are checkout-only operations anyway; nothing else needs the pin."""
    pyproject = REPO_ROOT / "pyproject.toml"
    if not pyproject.is_file():
        return None, None
    config = tomllib.loads(pyproject.read_text(encoding="utf-8"))
    pin = config["tool"]["dnd5e-database-sqlite"]
    return pin["upstream-repo"], pin["upstream-tag"]


UPSTREAM_REPO, UPSTREAM_TAG = _load_pin()


def tarball_url(tag: str) -> str:
    return f"https://github.com/{UPSTREAM_REPO}/archive/refs/tags/{tag}.tar.gz"


def download_tarball(tag: str, dest: Path) -> None:
    request = urllib.request.Request(
        tarball_url(tag), headers={"User-Agent": "5e-database-sqlite"}
    )
    with urllib.request.urlopen(request, timeout=60) as response, dest.open("wb") as out:
        shutil.copyfileobj(response, out)


def extract_src(tarball: Path, dest: Path) -> int:
    """Extract the archive's src/ subtree into dest, stripping the leading
    ``<repo>-<version>/src/`` prefix GitHub archives carry.

    Returns the number of files extracted.
    """
    count = 0
    with tarfile.open(tarball, "r:gz") as tar:
        for member in tar:
            parts = member.name.split("/")
            if len(parts) < 3 or parts[1] != "src":
                continue
            if not (member.isfile() or member.isdir()):
                continue
            member.name = "/".join(parts[2:])
            tar.extract(member, dest, filter="data")
            count += member.isfile()
    return count


def fetch(tag: str = UPSTREAM_TAG, data_dir: Path = DATA_DIR, force: bool = False) -> Path:
    """Ensure data/ holds the upstream src/ tree for `tag`; download if needed.

    The .upstream-tag marker is written only after the tree is fully in
    place, so an interrupted run never leaves data/ claiming to be complete
    (and the tree is git-tracked — any mess is one checkout away from clean).
    """
    if vendored_tag(data_dir) == tag and not force:
        print(f"{data_dir} already holds {tag}, skipping download")
        return data_dir

    data_dir.mkdir(parents=True, exist_ok=True)
    marker = data_dir / MARKER_NAME
    with tempfile.TemporaryDirectory(dir=data_dir) as tmp:
        tarball = Path(tmp) / f"{tag}.tar.gz"
        print(f"downloading {tarball_url(tag)}")
        download_tarball(tag, tarball)

        extracted = Path(tmp) / "src"
        count = extract_src(tarball, extracted)
        if count == 0:
            raise RuntimeError(f"no src/ files found in tarball for {tag}")

        marker.unlink(missing_ok=True)
        for old in data_dir.iterdir():
            if old.is_dir() and old != Path(tmp):
                shutil.rmtree(old)
        for entry in extracted.iterdir():
            entry.rename(data_dir / entry.name)
    marker.write_text(f"{tag}\n", encoding="utf-8")

    print(f"extracted {count} files to {data_dir} ({tag})")
    return data_dir


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--tag", default=UPSTREAM_TAG, help="upstream tag to fetch")
    parser.add_argument(
        "--force", action="store_true", help="re-download even if already present"
    )
    args = parser.parse_args()
    fetch(tag=args.tag, force=args.force)


if __name__ == "__main__":
    main()
