"""
Core SpendLil client — configuration, HTTP, error types, and factory methods.
"""
from __future__ import annotations

import json
from dataclasses import dataclass, field
from typing import Any, AsyncGenerator, Dict, Optional

import httpx


# ─── Config & Meta ────────────────────────────────────────


@dataclass
class SpendLilConfig:
    """Configuration for the SpendLil proxy client."""
    agent_id: str
    """Your SpendLil agent ID (X-SpendLil-Agent header)."""
    base_url: str
    """SpendLil API base URL, e.g. https://spendlil.yourco.com/api"""
    session_key: Optional[str] = None
    """Optional session key for context management (STICKY/POOL modes)."""
    timeout: float = 60.0
    """Request timeout in seconds."""


@dataclass
class SpendLilMeta:
    """Metadata returned by the SpendLil proxy in response headers."""
    tracked: bool = False
    model: Optional[str] = None
    provider: Optional[str] = None
    tier: Optional[str] = None
    fallback: bool = False
    original_model: Optional[str] = None
    escalated: bool = False
    escalated_from: Optional[str] = None
    translated: bool = False
    session_key: Optional[str] = None
    context_mode: Optional[str] = None
    quota_exceeded: bool = False

    @classmethod
    def from_headers(cls, headers: httpx.Headers) -> "SpendLilMeta":
        def h(key: str) -> Optional[str]:
            return headers.get(key)

        return cls(
            tracked=h("x-spendlil-tracked") == "true",
            model=h("x-spendlil-model"),
            provider=h("x-spendlil-provider"),
            tier=h("x-spendlil-tier"),
            fallback=h("x-spendlil-fallback") == "true",
            original_model=h("x-spendlil-original-model"),
            escalated=h("x-spendlil-escalated") == "true",
            escalated_from=h("x-spendlil-escalated-from"),
            translated=h("x-spendlil-translated") == "true",
            session_key=h("x-spendlil-session"),
            context_mode=h("x-spendlil-context-mode"),
            quota_exceeded=h("x-spendlil-quota") == "exceeded",
        )


# ─── Error ────────────────────────────────────────────────


class SpendLilError(Exception):
    """Raised when the SpendLil proxy returns an error response."""

    def __init__(
        self,
        status_code: int,
        message: str,
        body: Any = None,
        meta: Optional[SpendLilMeta] = None,
    ):
        super().__init__(f"HTTP {status_code}: {message}")
        self.status_code = status_code
        self.message = message
        self.body = body
        self.meta = meta


# ─── Low-level HTTP ───────────────────────────────────────


def _build_headers(
    agent_id: str,
    extra: Dict[str, str],
    session_key: Optional[str],
    priority: Optional[str],
) -> Dict[str, str]:
    headers = {
        "Content-Type": "application/json",
        "X-SpendLil-Agent": agent_id,
        **extra,
    }
    if session_key:
        headers["X-SpendLil-Session"] = session_key
    if priority:
        headers["X-SpendLil-Priority"] = priority
    return headers


def proxy_request(
    cfg: SpendLilConfig,
    path: str,
    body: Any,
    extra_headers: Dict[str, str],
    session_key: Optional[str] = None,
    priority: Optional[str] = None,
) -> tuple[int, httpx.Headers, Any]:
    """Synchronous proxy request. Returns (status, headers, body)."""
    url = cfg.base_url.rstrip("/") + path
    headers = _build_headers(cfg.agent_id, extra_headers, session_key, priority)

    with httpx.Client(timeout=cfg.timeout) as client:
        resp = client.post(url, headers=headers, content=json.dumps(body))

    try:
        resp_body = resp.json()
    except Exception:
        resp_body = {"error": "Non-JSON response"}

    return resp.status_code, resp.headers, resp_body


async def async_proxy_request(
    cfg: SpendLilConfig,
    path: str,
    body: Any,
    extra_headers: Dict[str, str],
    session_key: Optional[str] = None,
    priority: Optional[str] = None,
) -> tuple[int, httpx.Headers, Any]:
    """Async proxy request. Returns (status, headers, body)."""
    url = cfg.base_url.rstrip("/") + path
    headers = _build_headers(cfg.agent_id, extra_headers, session_key, priority)

    async with httpx.AsyncClient(timeout=cfg.timeout) as client:
        resp = await client.post(url, headers=headers, content=json.dumps(body))

    try:
        resp_body = resp.json()
    except Exception:
        resp_body = {"error": "Non-JSON response"}

    return resp.status_code, resp.headers, resp_body


async def async_stream_sse(
    cfg: SpendLilConfig,
    path: str,
    body: Any,
    extra_headers: Dict[str, str],
    session_key: Optional[str] = None,
) -> AsyncGenerator[Any, None]:
    """Async SSE stream — yields parsed JSON objects."""
    url = cfg.base_url.rstrip("/") + path
    headers = _build_headers(cfg.agent_id, extra_headers, session_key, None)
    headers["Accept"] = "text/event-stream"

    async with httpx.AsyncClient(timeout=cfg.timeout * 2) as client:
        async with client.stream("POST", url, headers=headers, content=json.dumps(body)) as resp:
            if resp.status_code >= 400:
                body_bytes = await resp.aread()
                try:
                    err_body = json.loads(body_bytes)
                    msg = err_body.get("error", {})
                    msg = msg.get("message", str(msg)) if isinstance(msg, dict) else str(msg)
                except Exception:
                    msg = f"HTTP {resp.status_code}"
                raise SpendLilError(resp.status_code, msg)

            async for line in resp.aiter_lines():
                if line.startswith("data: "):
                    data = line[6:].strip()
                    if data == "[DONE]":
                        return
                    try:
                        yield json.loads(data)
                    except json.JSONDecodeError:
                        yield data


# ─── Main client ──────────────────────────────────────────


class SpendLil:
    """
    SpendLil proxy client.

    Usage::

        sl = SpendLil(agent_id="xxx", base_url="https://spendlil.yourco.com/api")
        openai = sl.openai(api_key=os.environ["OPENAI_API_KEY"])
        response = openai.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": "Hello!"}],
        )
    """

    def __init__(
        self,
        agent_id: str,
        base_url: str,
        session_key: Optional[str] = None,
        timeout: float = 60.0,
    ):
        if not agent_id:
            raise ValueError("SpendLil: agent_id is required")
        if not base_url:
            raise ValueError("SpendLil: base_url is required")
        self._cfg = SpendLilConfig(
            agent_id=agent_id,
            base_url=base_url,
            session_key=session_key,
            timeout=timeout,
        )

    @property
    def config(self) -> SpendLilConfig:
        return self._cfg

    def openai(self, api_key: str) -> "SpendLilOpenAI":
        """Return an OpenAI-compatible client routed through SpendLil."""
        from .openai_client import SpendLilOpenAI
        return SpendLilOpenAI(self._cfg, api_key)

    def anthropic(self, api_key: str) -> "SpendLilAnthropic":
        """Return an Anthropic-compatible client routed through SpendLil."""
        from .anthropic_client import SpendLilAnthropic
        return SpendLilAnthropic(self._cfg, api_key)

    def with_session(self, session_key: str) -> "SpendLil":
        """Return a new SpendLil instance with a specific session key."""
        return SpendLil(
            agent_id=self._cfg.agent_id,
            base_url=self._cfg.base_url,
            session_key=session_key,
            timeout=self._cfg.timeout,
        )

    def raw(
        self,
        provider: str,
        path: str,
        body: Any,
        extra_headers: Optional[Dict[str, str]] = None,
        session_key: Optional[str] = None,
        priority: Optional[str] = None,
    ) -> tuple[int, httpx.Headers, Any]:
        """Low-level synchronous proxy fetch."""
        return proxy_request(
            self._cfg,
            f"/proxy/{provider}{path}",
            body,
            extra_headers or {},
            session_key or self._cfg.session_key,
            priority,
        )

    async def async_raw(
        self,
        provider: str,
        path: str,
        body: Any,
        extra_headers: Optional[Dict[str, str]] = None,
        session_key: Optional[str] = None,
        priority: Optional[str] = None,
    ) -> tuple[int, httpx.Headers, Any]:
        """Low-level async proxy fetch."""
        return await async_proxy_request(
            self._cfg,
            f"/proxy/{provider}{path}",
            body,
            extra_headers or {},
            session_key or self._cfg.session_key,
            priority,
        )
