"""
CandyClient — Base client for the cedric-8EF API.
Automatically fetches the live API URL from candy.json.
"""

import httpx
import json
from typing import Optional, Generator

CANDY_JSON_URL = "https://client.hubworld.net/candy.json"

class CandyUnavailableError(Exception):
    pass

class CandyQuotaError(Exception):
    pass

class CandyClient:
    """
    Low-level client. You usually don't need to instantiate this directly —
    use the module classes (Math, Coding, etc.) instead.
    """

    def __init__(self, timeout: int = 120):
        self.timeout = timeout
        self._api_url: Optional[str] = None

    def _resolve_url(self) -> str:
        """Fetch the live tunnel URL from candy.json."""
        if self._api_url:
            return self._api_url
        try:
            resp = httpx.get(CANDY_JSON_URL, timeout=10)
            resp.raise_for_status()
            data = resp.json()
        except Exception as e:
            raise CandyUnavailableError(f"Cannot reach candy.json: {e}")

        if data.get("is_closed", True):
            raise CandyUnavailableError("The cedric-8EF API is currently offline.")

        link = data.get("link", "").strip()
        if not link:
            raise CandyUnavailableError("No API link found in candy.json.")

        self._api_url = link.rstrip("/")
        return self._api_url

    def ask(self, personality: str, prompt: str, context: Optional[str] = None, stream: bool = False):
        """
        Send a prompt to a given personality.
        Returns the response string, or a generator if stream=True.
        """
        base = self._resolve_url()
        url = f"{base}/ask/{personality}"

        payload = {"prompt": prompt, "stream": stream}
        if context:
            payload["context"] = context

        if stream:
            return self._stream(url, payload)
        else:
            return self._post(url, payload)

    def _post(self, url: str, payload: dict) -> dict:
        try:
            resp = httpx.post(url, json=payload, timeout=self.timeout)
        except httpx.ConnectError:
            self._api_url = None  # reset cache
            raise CandyUnavailableError("Cannot connect to cedric-8EF API.")

        if resp.status_code == 429:
            raise CandyQuotaError("Daily quota exceeded. Try again tomorrow.")
        if resp.status_code == 404:
            raise ValueError(f"Unknown personality. Check candy.personalities()")
        resp.raise_for_status()
        return resp.json()

    def _stream(self, url: str, payload: dict) -> Generator[str, None, None]:
        with httpx.stream("POST", url, json=payload, timeout=self.timeout) as resp:
            if resp.status_code == 429:
                raise CandyQuotaError("Daily quota exceeded.")
            resp.raise_for_status()
            for line in resp.iter_lines():
                if line:
                    try:
                        data = json.loads(line)
                        token = data.get("response", "")
                        if token:
                            yield token
                    except json.JSONDecodeError:
                        pass

    def quota(self) -> dict:
        """Check your remaining quota for today."""
        base = self._resolve_url()
        resp = httpx.get(f"{base}/quota/status", timeout=10)
        resp.raise_for_status()
        return resp.json()

    def personalities(self) -> list[str]:
        """List all available personalities."""
        base = self._resolve_url()
        resp = httpx.get(f"{base}/personalities", timeout=10)
        resp.raise_for_status()
        return resp.json()["personalities"]
