"""
odss-api — client Python ufficiale (single-file, zero dipendenze extra).

Quote sportive dei bookmaker italiani ADM (più bet365, Betfair Exchange,
Pinnacle) in uno schema JSON stabile. Contratto: https://odss-api.com/openapi.json

Uso:
    from odss_api import OdssApi

    api = OdssApi("odss_live_...")                    # la tua chiave
    print(api.sports())                             # sport disponibili
    page = api.odds(sport="calcio", league="serie a", limit=50)
    for rec in page["odds"]:
        print(rec["event"], rec["market"], rec["bookmakers"])

    # tutto il palinsesto, paginato in automatico:
    for rec in api.iter_odds(sport="calcio"):
        ...

    # quote LIVE (in-play): ogni record ha state="live", score e minute correnti
    live = api.odds(state="live", sport="calcio")

Richiede solo la libreria standard (urllib). Se preferisci requests, la logica
è identica: GET su BASE_URL + header x-api-key.
"""
from __future__ import annotations

import json
import time
import urllib.error
import urllib.parse
import urllib.request
from typing import Any, Dict, Iterator, Optional

__version__ = "1.0.0"
DEFAULT_BASE_URL = "https://odss-api.com/api/v1"


class OdssApiError(RuntimeError):
    """Errore HTTP dall'API (status + corpo)."""

    def __init__(self, status: int, body: str):
        self.status = status
        self.body = body
        super().__init__(f"odss-api HTTP {status}: {body[:200]}")


class OdssApi:
    def __init__(
        self,
        api_key: str,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = 30.0,
        max_retries: int = 3,
    ) -> None:
        if not api_key:
            raise ValueError("api_key mancante")
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self.max_retries = max_retries

    # -- HTTP ----------------------------------------------------------------
    def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        query = {k: v for k, v in (params or {}).items() if v is not None}
        url = f"{self.base_url}{path}"
        if query:
            url += "?" + urllib.parse.urlencode(query)
        req = urllib.request.Request(url, method="GET")
        req.add_header("x-api-key", self.api_key)
        req.add_header("accept", "application/json")
        req.add_header("user-agent", f"odss-api-python/{__version__}")
        last_exc: Optional[Exception] = None
        for attempt in range(self.max_retries):
            try:
                with urllib.request.urlopen(req, timeout=self.timeout) as resp:
                    return json.loads(resp.read().decode("utf-8"))
            except urllib.error.HTTPError as exc:
                body = exc.read().decode("utf-8", "replace")
                # 429 = rate/quota: rispetta Retry-After e ritenta; 5xx = ritenta.
                if exc.code == 429 or 500 <= exc.code < 600:
                    if attempt < self.max_retries - 1:
                        wait = float(exc.headers.get("Retry-After") or (2 ** attempt))
                        time.sleep(min(wait, 30.0))
                        continue
                raise OdssApiError(exc.code, body) from exc
            except urllib.error.URLError as exc:  # rete: ritenta
                last_exc = exc
                if attempt < self.max_retries - 1:
                    time.sleep(2 ** attempt)
                    continue
                raise
        if last_exc:
            raise last_exc
        raise RuntimeError("unreachable")

    # -- Endpoint ------------------------------------------------------------
    def odds(
        self,
        sport: Optional[str] = None,
        market: Optional[str] = None,
        league: Optional[str] = None,
        bookmakers: Optional[str] = None,
        event_id: Optional[str] = None,
        state: Optional[str] = None,  # "prematch" (default) | "live" | "all"
        limit: int = 500,
        offset: int = 0,
    ) -> Dict[str, Any]:
        """Quote cross-book per evento/mercato. Ritorna {count, offset, returned,
        state, odds:[...]} (+ live_enabled quando state è live/all)."""
        return self._get(
            "/odds",
            {
                "sport": sport,
                "market": market,
                "league": league,
                "bookmakers": bookmakers,
                "event_id": event_id,
                "state": state,
                "limit": limit,
                "offset": offset,
            },
        )

    def iter_odds(self, page_size: int = 1000, **filters: Any) -> Iterator[Dict[str, Any]]:
        """Itera l'INTERO risultato paginando in automatico (offset += returned
        finché offset+returned < count). filters = gli stessi di odds()."""
        offset = 0
        while True:
            page = self.odds(limit=page_size, offset=offset, **filters)
            odds = page.get("odds", [])
            for rec in odds:
                yield rec
            returned = page.get("returned", len(odds))
            count = page.get("count", 0)
            offset += returned
            if returned == 0 or offset >= count:
                break

    def live_odds(self, **filters: Any) -> Dict[str, Any]:
        """Scorciatoia per le quote in-play (state='live')."""
        return self.odds(state="live", **filters)

    def sports(self) -> list:
        return self._get("/sports").get("sports", [])

    def bookmakers(self) -> list:
        return self._get("/bookmakers").get("bookmakers", [])

    def leagues(self) -> Dict[str, Any]:
        """Palinsesto pubblico (sport → leghe con conteggi). Non richiede chiave."""
        return self._get("/leagues")

    def coverage(self) -> Dict[str, Any]:
        """Conteggio catalogo book per regione. Non richiede chiave."""
        return self._get("/coverage")

    def status(self) -> Dict[str, Any]:
        """Stato operativo + distribuzione reale della freschezza (p50/p95 per
        book su 24h). Non richiede chiave. Vista umana: /status sul sito."""
        return self._get("/status")


if __name__ == "__main__":
    import os
    import sys

    key = os.environ.get("ODSS_API_KEY") or (sys.argv[1] if len(sys.argv) > 1 else "")
    if not key:
        print("Uso: ODSS_API_KEY=odss_live_... python odss_api.py", file=sys.stderr)
        raise SystemExit(2)
    client = OdssApi(key)
    print("Sport nel feed:", ", ".join(client.sports()))
    demo = client.odds(sport="calcio", limit=1)
    print(json.dumps(demo, indent=2, ensure_ascii=False)[:1200])
