"""
client.py — Typed HTTP client for Headless Oracle V5.

All methods return parsed dicts matching the Oracle response schemas.
Use verify() from headless_oracle.verify to cryptographically verify receipts.

Example:
    from headless_oracle import OracleClient, verify

    client = OracleClient(api_key="ok_live_...")
    receipt = client.get_status("XNYS")
    result = verify(receipt)
    assert result.valid
    if receipt["status"] != "OPEN":
        halt_trading()
"""

from __future__ import annotations

from typing import Any

import httpx

_DEFAULT_BASE = "https://headlessoracle.com"


class OracleError(Exception):
    """Raised when the Oracle returns a non-2xx response."""
    def __init__(self, status_code: int, body: dict[str, Any]):
        self.status_code = status_code
        self.body = body
        super().__init__(f"Oracle error {status_code}: {body.get('error', body)}")


class OracleClient:
    """
    HTTP client for Headless Oracle V5.

    Args:
        api_key:  X-Oracle-Key value for authenticated endpoints (/v5/status, /v5/batch).
        base_url: Override base URL (useful for staging).
        timeout:  HTTP timeout in seconds.
    """

    def __init__(
        self,
        api_key: str | None = None,
        base_url: str = _DEFAULT_BASE,
        timeout: float = 10.0,
    ):
        headers = {"Accept": "application/json"}
        if api_key:
            headers["X-Oracle-Key"] = api_key
        self._http = httpx.Client(base_url=base_url, headers=headers, timeout=timeout)

    # ── Public routes (no auth) ───────────────────────────────────────────────

    def get_demo(self, mic: str = "XNYS") -> dict[str, Any]:
        """Fetch a signed demo receipt (no auth). Suitable for integration testing."""
        return self._get("/v5/demo", params={"mic": mic})

    def get_schedule(self, mic: str = "XNYS") -> dict[str, Any]:
        """Fetch next open/close times for an exchange (no auth)."""
        return self._get("/v5/schedule", params={"mic": mic})

    def list_exchanges(self) -> dict[str, Any]:
        """Fetch the directory of all supported exchanges (no auth)."""
        return self._get("/v5/exchanges")

    def get_keys(self) -> dict[str, Any]:
        """Fetch the public key registry and canonical payload spec (no auth)."""
        return self._get("/v5/keys")

    def get_health(self) -> dict[str, Any]:
        """Fetch a signed liveness receipt from the oracle (no auth)."""
        return self._get("/v5/health")

    # ── Authenticated routes ──────────────────────────────────────────────────

    def get_status(self, mic: str = "XNYS") -> dict[str, Any]:
        """
        Fetch a signed live receipt (requires api_key).
        Raises OracleError(401) if no api_key was provided.
        Raises OracleError(403) if the key is invalid.
        """
        return self._get("/v5/status", params={"mic": mic})

    def get_batch(self, mics: list[str]) -> dict[str, Any]:
        """
        Fetch signed receipts for multiple MICs in one request (requires api_key).
        Receipts are returned in the same order as the input list.
        """
        return self._get("/v5/batch", params={"mics": ",".join(mics)})

    # ── Internal ──────────────────────────────────────────────────────────────

    def _get(self, path: str, params: dict[str, str] | None = None) -> dict[str, Any]:
        resp = self._http.get(path, params=params)
        body = resp.json()
        if resp.status_code >= 400:
            raise OracleError(resp.status_code, body)
        return body

    def close(self) -> None:
        self._http.close()

    def __enter__(self) -> "OracleClient":
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()
