#!/usr/bin/env python3
"""Small e-Gov API v2 client. Python 3.10+, standard library only. MIT license.

Search one page, inspect its IDs, then explicitly choose one law to download.
See https://codeagent.jp/posts/egov-law-api-v2-python/ for the walkthrough.
"""
import argparse
import base64
import binascii
from datetime import date, datetime, timezone
import json
from pathlib import Path
import sys
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen
import xml.etree.ElementTree as ET

BASE = "https://laws.e-gov.go.jp/api/2"


def fetch_json(path, params):
    url = f"{BASE}/{path}?{urlencode(params)}"
    request = Request(url, headers={"Accept": "application/json"})
    with urlopen(request, timeout=30) as response:
        data = json.load(response)
    if not isinstance(data, dict):
        raise ValueError("Expected a JSON object; check the current API schema")
    return url, data


def positive_limit(value):
    number = int(value)
    if not 1 <= number <= 20:
        raise argparse.ArgumentTypeError("Use 1..20 for this small example")
    return number


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--out", type=Path, default=Path("egov-results"))
    commands = parser.add_subparsers(dest="command", required=True)
    search = commands.add_parser("search", help="Search one page; no auto-download")
    search.add_argument("title")
    search.add_argument("--limit", type=positive_limit, default=3)
    search.add_argument("--offset", type=int, default=0)
    get = commands.add_parser("get", help="Download one explicitly chosen law ID")
    get.add_argument("law_id")
    get.add_argument("--asof", type=date.fromisoformat)
    args = parser.parse_args()
    params = {"response_format": "json"}
    if args.command == "search":
        if args.offset < 0:
            parser.error("offset must be nonnegative")
        params.update(law_title=args.title, limit=args.limit, offset=args.offset)
        path = "laws"
    else:
        # Keep the response envelope as JSON and preserve the law text as XML.
        params["law_full_text_format"] = "xml"
        if args.asof:
            params["asof"] = args.asof.isoformat()
        path = "law_data/" + quote(args.law_id, safe="")
    url, data = fetch_json(path, params)
    if args.command == "search":
        if not isinstance(data.get("laws"), list):
            raise ValueError("Missing laws list; check the API response")
    else:
        if not data.get("law_info", {}).get("law_id"):
            raise ValueError("Missing law ID; response was not saved as a success")
        if not data.get("revision_info", {}).get("law_revision_id"):
            raise ValueError("Missing revision ID; check the API response")
        # Mixed response/text formats are Base64 encoded by the API.
        xml_bytes = base64.b64decode(data["law_full_text"], validate=True)
        ET.fromstring(xml_bytes)
    stamp = datetime.now(timezone.utc)
    args.out.mkdir(parents=True, exist_ok=True)
    stem = args.out / f"{args.command}-{stamp.strftime('%Y%m%dT%H%M%S%fZ')}"
    envelope = {"retrieved_at": stamp.isoformat(), "request_url": url,
                "requested_asof": params.get("asof"), "response": data}
    with stem.with_suffix(".json").open("x", encoding="utf-8") as file:
        json.dump(envelope, file, ensure_ascii=False, indent=2)
    if args.command == "search":
        print(f"count={data.get('count')} total_count={data.get('total_count')} "
              f"next_offset={data.get('next_offset')}")
        for law in data["laws"]:
            print(law["law_info"]["law_id"], law["revision_info"]["law_title"])
    else:
        with stem.with_suffix(".xml").open("xb") as file:
            file.write(xml_bytes)
        print(data["law_info"]["law_id"], data["revision_info"]["law_title"])
        print("revision:", data["revision_info"]["law_revision_id"])
    print("saved:", stem.with_suffix(".json"))


if __name__ == "__main__":
    try:
        main()
    except HTTPError as error:
        print(f"HTTP {error.code}: check parameters, service status and API docs; "
              "no automatic retry", file=sys.stderr)
        sys.exit(1)
    except (URLError, TimeoutError, ValueError, KeyError, TypeError, OSError,
            ET.ParseError, binascii.Error) as error:
        print(f"Stopped: {type(error).__name__}: {error}", file=sys.stderr)
        sys.exit(1)
