# -*- coding: utf-8 -*-
"""Japan Places API - Python client.

Free endpoints work with no key and no registration (attribution required).
Paid endpoints use x402: the 402 response tells you the exact amount, you sign
a USDC (Base) transfer authorization, then retry with the PAYMENT-SIGNATURE
header.

Signing requires `eth-account`:
    pip install requests eth-account

Usage:
    from japan_places import JapanPlacesClient

    client = JapanPlacesClient()
    print(client.municipality_census(prefecture="東京"))   # free
    print(client.quality_scores())                         # free

    paid = JapanPlacesClient(private_key="0x...")          # x402
    print(paid.station_catchment(limit=10))                # $0.05/call
"""
import base64
import json
from typing import Any, Dict, List, Optional
from urllib.parse import quote

import requests

BASE_URL = "https://koteihi-lab.local-connect-design.trade/api/v1"


def b64url_json(obj: Any) -> str:
    """Base64url-encode a JSON object, UTF-8 safe.

    Note: the plain `base64.b64encode(json.dumps(obj).encode())` form breaks
    on non-ASCII service metadata, so encode UTF-8 explicitly.
    """
    raw = json.dumps(obj, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
    return base64.urlsafe_b64encode(raw).decode("ascii").rstrip("=")


class JapanPlacesClient:
    """Client for the Japan Places API (2.63M Japanese POIs, 5 derived signals)."""

    def __init__(self, private_key: Optional[str] = None, timeout: int = 30):
        self.base_url = BASE_URL
        self.private_key = private_key
        self.timeout = timeout
        self.session = requests.Session()
        self.session.headers.update(
            {"User-Agent": "japan-places-python/1.0", "Accept": "application/json"}
        )

    # ------------------------------------------------------------------ #
    # internals
    # ------------------------------------------------------------------ #
    def _get(self, path: str, params: Optional[Dict[str, Any]] = None, paid: bool = False):
        headers = {}
        if paid and self.private_key:
            headers["PAYMENT-SIGNATURE"] = self._sign(path)
        resp = self.session.get(
            self.base_url + path, params=params or {}, headers=headers, timeout=self.timeout
        )
        if resp.status_code == 402:
            raise PaymentRequired(resp)
        resp.raise_for_status()
        return resp.json()

    def _sign(self, path: str) -> str:
        """Build the x402 PAYMENT-SIGNATURE header.

        A real implementation signs a USDC `transferWithAuthorization`
        (EIP-3009) for the amount in the 402 body. This helper produces the
        payload structure; fill in `signature` from your signer.
        """
        probe = self.session.get(self.base_url + path, timeout=self.timeout)
        if probe.status_code != 402:
            raise RuntimeError("expected 402 before signing, got %s" % probe.status_code)
        requirements = probe.json()["accepts"][0]
        payload = {
            "x402Version": probe.json().get("x402Version", 2),
            "scheme": requirements["scheme"],
            "network": requirements["network"],
            "payload": {
                "signature": "<fill-in>",
                "authorization": {
                    "from": "<fill-in>",
                    "to": requirements["payTo"],
                    "value": requirements["amount"],
                    "validAfter": "<fill-in>",
                    "validBefore": "<fill-in>",
                    "nonce": "<fill-in>",
                },
            },
        }
        return b64url_json(payload)

    # ------------------------------------------------------------------ #
    # free endpoints (attribution required)
    # ------------------------------------------------------------------ #
    def municipality_census(self, prefecture: Optional[str] = None) -> Dict[str, Any]:
        """G-1: POI counts by prefecture and 16 categories."""
        params = {"prefecture": prefecture} if prefecture else {}
        return self._get("/poi/municipality", params=params)

    def quality_scores(self) -> Dict[str, Any]:
        """G-3: geocode quality distribution (0-100)."""
        return self._get("/poi/quality")

    def health(self) -> Dict[str, Any]:
        return self._get("/health")

    # ------------------------------------------------------------------ #
    # paid endpoints (x402, per call)
    # ------------------------------------------------------------------ #
    def station_catchment(self, limit: int = 20) -> Dict[str, Any]:
        """P-4: station catchment facility density. $0.05/call."""
        return self._get("/poi/catchment", params={"limit": limit}, paid=True)

    def chain_stores(self, min_count: int = 5, limit: int = 50) -> Dict[str, Any]:
        """P-5: chain store index. $0.03/call."""
        return self._get("/poi/chains", params={"min_count": min_count, "limit": limit}, paid=True)

    def aliveness_index(self, min_score: int = 0, limit: int = 100) -> Dict[str, Any]:
        """P-6: aliveness index (open/closed probability). $0.02/call."""
        return self._get("/poi/aliveness", params={"min_score": min_score, "limit": limit}, paid=True)


class PaymentRequired(Exception):
    """Raised when the API returns 402 Payment Required."""

    def __init__(self, response):
        self.response = response
        self.body = None
        try:
            self.body = response.json()
        except Exception:
            pass
        amount = None
        try:
            amount = self.body["accepts"][0]["amount"]
        except Exception:
            pass
        self.amount = amount
        self.payment_required_header = response.headers.get("PAYMENT-REQUIRED")
        super().__init__((
            "Payment required (amount=%s). Sign a USDC (Base) transfer "
            "authorization and retry with the PAYMENT-SIGNATURE header."
        ) % amount)


def main():
    client = JapanPlacesClient()
    print("=== health ===")
    print(json.dumps(client.health(), indent=2, ensure_ascii=False))
    print("\n=== municipality census (Tokyo) ===")
    tokyo = client.municipality_census(prefecture="東京")
    print(json.dumps(tokyo, indent=2, ensure_ascii=False)[:500])
    print("\n=== quality ===")
    print(json.dumps(client.quality_scores(), indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main()