"""LM Studio (local) and Grok (online fallback) client wrappers.

LM Studio: always available, CPU inference via GGUF models.
Grok: only callable when hybrid mode + user confirmation + env var set.
"""

import os
from typing import Optional

import httpx
import yaml

from utils.errors import LLMServiceError, GrokServiceError, ConfigError


class LocalLLMClient:
    """Client for LM Studio local inference."""

    def __init__(self, config_path: str = "config.yaml"):
        with open(config_path) as f:
            cfg = yaml.safe_load(f)

        llm_cfg = cfg["models"]["local_llm"]
        self.endpoint = f"{llm_cfg['base_url']}/chat/completions"
        self.model = llm_cfg["model"]
        self.temperature = llm_cfg["temperature"]
        self.max_tokens = llm_cfg["max_tokens"]
        self.timeout = llm_cfg["timeout_sec"]

    def generate(self, prompt: str, system_prompt: Optional[str] = None) -> str:
        """Generate completion from LM Studio."""
        messages = []
        if system_prompt:
            messages.append({"role": "system", "content": system_prompt})
        messages.append({"role": "user", "content": prompt})

        payload = {
            "model": self.model,
            "messages": messages,
            "temperature": self.temperature,
            "max_tokens": self.max_tokens,
            "stream": False
        }

        try:
            response = httpx.post(self.endpoint, json=payload, timeout=self.timeout)
            response.raise_for_status()
            data = response.json()
            return data["choices"][0]["message"]["content"]
        except httpx.TimeoutException:
            raise LLMServiceError(
                f"LLM timed out after {self.timeout}s",
                details={"endpoint": self.endpoint, "model": self.model}
            )
        except httpx.HTTPError as e:
            raise LLMServiceError(
                f"LLM HTTP error: {e}",
                details={"endpoint": self.endpoint}
            )
        except (KeyError, IndexError) as e:
            raise LLMServiceError(
                f"Unexpected LLM response format: {e}",
                details={"endpoint": self.endpoint}
            )


class GrokClient:
    """Client for Grok API (xAI). Only usable in hybrid mode with user confirmation."""

    def __init__(self, config_path: str = "config.yaml"):
        with open(config_path) as f:
            cfg = yaml.safe_load(f)

        grok_cfg = cfg["models"]["grok"]
        self.enabled = grok_cfg.get("enabled", False)
        self.endpoint = f"{grok_cfg['base_url']}/chat/completions"
        self.model = grok_cfg["model"]
        self.temperature = grok_cfg["temperature"]
        self.max_tokens = grok_cfg["max_tokens"]
        self.timeout = grok_cfg["timeout_sec"]
        self.app_mode = cfg["app"]["mode"]

        # Validate env var
        self.api_key = os.environ.get("GROK_API_KEY")

    def is_available(self) -> bool:
        """Check if Grok is available (enabled + hybrid mode + API key)."""
        return (
            self.enabled and
            self.app_mode == "hybrid" and
            self.api_key is not None
        )

    def generate(self, prompt: str, system_prompt: Optional[str] = None) -> str:
        """Generate completion from Grok API.

        Pre-conditions (enforced by graph topology, checked here defensively):
        - app.mode == "hybrid"
        - models.grok.enabled == true
        - GROK_API_KEY env var is set
        """
        if not self.enabled:
            raise GrokServiceError("Grok is disabled in config")
        if self.app_mode != "hybrid":
            raise GrokServiceError("Grok requires app.mode == 'hybrid'")
        if not self.api_key:
            raise GrokServiceError(
                "GROK_API_KEY environment variable not set",
                details={"hint": "export GROK_API_KEY=your_key"}
            )

        messages = []
        if system_prompt:
            messages.append({"role": "system", "content": system_prompt})
        messages.append({"role": "user", "content": prompt})

        payload = {
            "model": self.model,
            "messages": messages,
            "temperature": self.temperature,
            "max_tokens": self.max_tokens,
        }

        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }

        try:
            response = httpx.post(
                self.endpoint,
                json=payload,
                headers=headers,
                timeout=self.timeout
            )
            response.raise_for_status()
            data = response.json()
            return data["choices"][0]["message"]["content"]
        except httpx.TimeoutException:
            raise GrokServiceError(f"Grok timed out after {self.timeout}s")
        except httpx.HTTPError as e:
            raise GrokServiceError(f"Grok HTTP error: {e}")
        except (KeyError, IndexError) as e:
            raise GrokServiceError(f"Unexpected Grok response format: {e}")
