"""avalos_sdk -- a thin Python client for the Avalos AI API (stdlib only).

    from avalos_sdk import Avalos
    ai = Avalos()                                   # key from AVALOS_API_KEY or ~/.avalos/config.json
    print(ai.chat("Explain CRDTs in two sentences", effort="star"))
    print(ai.code("app.py", open("app.py").read(), "fix the off-by-one"))
    print(ai.vision("photo.png", "What colour is the car?"))

Effort ladder: quick (T1) · star (T2, default) · galaxy (T3) · cosmos (T4) · quantum (T5).
The raw endpoint is POST {base}/v1/chat/completions (standard chat-completions format), so tool calling
works with the `tools` + `client_tools` fields (see avalos_agent.py for a full loop).
"""
import base64
import json
import mimetypes
import os
import time
import urllib.error
import urllib.request
from pathlib import Path

__version__ = "0.1.0"


class AvalosError(RuntimeError):
    def __init__(self, status, payload):
        self.status, self.payload = status, payload
        msg = payload.get("message") or payload.get("error") if isinstance(payload, dict) else payload
        super().__init__("HTTP %s: %s" % (status, msg))


class Avalos:
    def __init__(self, api_key=None, base_url=None, timeout=600):
        cfg = {}
        try:
            cfg = json.loads((Path.home() / ".avalos" / "config.json").read_text(encoding="utf-8"))
        except (OSError, ValueError):
            pass
        self.api_key = api_key or os.environ.get("AVALOS_API_KEY") or cfg.get("api_key") or ""
        self.base = (base_url or os.environ.get("AVALOS_BASE") or "https://avalos.ai").rstrip("/")
        self.timeout = timeout

    def _call(self, method, path, body=None):
        data = json.dumps(body).encode() if body is not None else None
        hdrs = {"X-Avalos-Key": self.api_key, "User-Agent": "avalos-sdk/" + __version__}
        if data is not None:
            hdrs["Content-Type"] = "application/json"
        req = urllib.request.Request(self.base + path, data=data, headers=hdrs, method=method)
        try:
            with urllib.request.urlopen(req, timeout=self.timeout) as r:
                return json.loads(r.read().decode("utf-8"))
        except urllib.error.HTTPError as e:
            raw = e.read().decode("utf-8", "replace")
            try:
                payload = json.loads(raw)
            except ValueError:
                payload = raw[:500]
            raise AvalosError(e.code, payload) from None

    def completions(self, messages, effort="star", **extra):
        """Raw chat-completions call. Returns the full response dict."""
        body = {"messages": messages, "effort": effort}
        body.update(extra)
        return self._call("POST", "/v1/chat/completions", body)

    def chat(self, prompt_or_messages, effort="star", system=None, **extra):
        """One answer as text. Accepts a string or a messages list."""
        if isinstance(prompt_or_messages, str):
            msgs = ([{"role": "system", "content": system}] if system else []) + \
                   [{"role": "user", "content": prompt_or_messages}]
        else:
            msgs = list(prompt_or_messages)
        r = self.completions(msgs, effort=effort, **extra)
        return ((r.get("choices") or [{}])[0].get("message") or {}).get("content") or ""

    def code(self, filename, content, task, mode="sota", poll_s=5, max_wait_s=1800):
        """Coder Engine: returns the edited file content (it does not touch your disk)."""
        rid = self._call("POST", "/v1/coder", {"task": task, "filename": filename,
                                               "content": content, "mode": mode})["run_id"]
        t0 = time.time()
        while True:
            s = self._call("GET", "/v1/coder/status?rid=" + rid)
            if s.get("status") not in ("running", None):
                if s.get("status") in ("SUCCESS", "DONE") and s.get("content") is not None:
                    return s["content"]
                raise AvalosError(s.get("status"), s)
            if time.time() - t0 > max_wait_s:
                raise AvalosError("timeout", {"error": "coder run %s still running" % rid})
            time.sleep(poll_s)

    def vision(self, image_path, question=""):
        p = Path(image_path)
        mime = mimetypes.guess_type(p.name)[0] or "image/png"
        b64 = base64.b64encode(p.read_bytes()).decode()
        return self._call("POST", "/v1/vision", {"image": b64, "mime": mime,
                                                  "question": question}).get("answer", "")
