"""Pull every Japanese anime (and every light novel) from AniList, year by year, and cache the raw pages.

Classification (isekai or not, first season or sequel) happens later in build_dataset.py, so the
definitions can change without asking the API again.

    python fetch_anilist.py anime 2000 2026
    python fetch_anilist.py novel 2000 2026
"""
import json
import sys
import time
from pathlib import Path

import requests

API = "https://graphql.anilist.co"
RAW = Path(__file__).resolve().parent.parent / "data" / "raw"

FIELDS = """
  id idMal type format status source countryOfOrigin isAdult
  title { english romaji native } synonyms
  startDate { year month day } season seasonYear episodes chapters volumes
  averageScore meanScore popularity favourites genres
  tags { name rank isMediaSpoiler }
  studios(isMain: true) { nodes { name } }
  relations { edges { relationType node { id type format } } }
"""

QUERIES = {
    "anime": """query ($page: Int, $from: FuzzyDateInt, $to: FuzzyDateInt) {
  Page(page: $page, perPage: 50) {
    pageInfo { hasNextPage }
    media(type: ANIME, countryOfOrigin: "JP", format_in: [TV, TV_SHORT, ONA, MOVIE, OVA],
          startDate_greater: $from, startDate_lesser: $to, sort: ID) { %s }
  }
}""" % FIELDS,
    "novel": """query ($page: Int, $from: FuzzyDateInt, $to: FuzzyDateInt) {
  Page(page: $page, perPage: 50) {
    pageInfo { hasNextPage }
    media(type: MANGA, format: NOVEL, countryOfOrigin: "JP",
          startDate_greater: $from, startDate_lesser: $to, sort: ID) { %s }
  }
}""" % FIELDS,
}


def post(query, variables):
    while True:
        r = requests.post(API, json={"query": query, "variables": variables}, timeout=60)
        if r.status_code == 429:
            wait = int(r.headers.get("Retry-After", 60)) + 1
            print(f"  rate limited, waiting {wait}s", flush=True)
            time.sleep(wait)
            continue
        if r.status_code >= 500:
            print(f"  server error {r.status_code}, retrying in 20s", flush=True)
            time.sleep(20)
            continue
        r.raise_for_status()
        if int(r.headers.get("X-RateLimit-Remaining", 10)) < 3:
            time.sleep(20)
        else:
            time.sleep(2.1)  # stays under the (currently degraded) 30 requests per minute
        return r.json()


def fetch(kind, first, last):
    out = RAW / kind
    out.mkdir(parents=True, exist_ok=True)
    for year in range(first, last + 1):
        page = 1
        while True:
            path = out / f"{year}_p{page:03d}.json"
            if path.exists():
                data = json.loads(path.read_text(encoding="utf-8"))
            else:
                data = post(QUERIES[kind], {"page": page, "from": year * 10000 - 1, "to": (year + 1) * 10000})
                if "errors" in data:
                    raise SystemExit(f"{year} page {page}: {data['errors']}")
                path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
            info = data["data"]["Page"]
            print(f"{kind} {year} page {page}: {len(info['media'])}", flush=True)
            if not info["pageInfo"]["hasNextPage"]:
                break
            page += 1


if __name__ == "__main__":
    fetch(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
