"""
CandyClient v3
"""

import httpx
import json
from typing import Optional, Generator

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

LANG_INSTRUCTIONS = {
    "FR": "Réponds toujours en français.",
    "EN": "Always respond in English.",
    "ES": "Responde siempre en español.",
    "DE": "Antworte immer auf Deutsch.",
    "IT": "Rispondi sempre in italiano.",
    "PT": "Responda sempre em português.",
    "ZH": "始终用中文回答。",
    "JA": "常に日本語で答えてください。",
    "AR": "أجب دائمًا باللغة العربية.",
    "RU": "Всегда отвечай на русском языке.",
    "NL": "Antwoord altijd in het Nederlands.",
    "PL": "Zawsze odpowiadaj po polsku.",
    "SV": "Svara alltid på svenska.",
    "TR": "Her zaman Türkçe yanıt ver.",
    "KO": "항상 한국어로 대답하세요.",
    "HI": "हमेशा हिंदी में जवाब दें।",
    "VI": "Luôn trả lời bằng tiếng Việt.",
    "ID": "Selalu jawab dalam bahasa Indonesia.",
    "CS": "Vždy odpovídej česky.",
    "RO": "Răspunde întotdeauna în română.",
}

STYLE_INSTRUCTIONS = {
    "default":   "",
    "concise":   "Be concise and direct. No filler. Get straight to the point.",
    "detailed":  "Be thorough. Include explanations, nuances, and concrete examples.",
    "bullet":    "Structure your response as bullet points.",
    "academic":  "Use a formal, structured, academic tone with clear sections.",
    "casual":    "Use a casual, friendly, conversational tone.",
    "technical": "Use precise technical language. Be exact.",
    "eli5":      "Explain as if talking to a 5-year-old. Use simple words and analogies.",
}

TONE_INSTRUCTIONS = {
    "neutral":     "",
    "encouraging": "Be positive, motivating, and encouraging.",
    "strict":      "Be direct and strict. No sugarcoating.",
    "humorous":    "Add a touch of humor where appropriate.",
    "empathetic":  "Be warm, empathetic, and understanding.",
    "socratic":    "Guide with questions rather than direct answers.",
}

FORMAT_INSTRUCTIONS = {
    "text":     "",
    "markdown": "Format using Markdown: headers (##), bold (**text**), lists, code blocks (```).",
    "json":     "Return a valid JSON object only. No prose outside the JSON.",
    "html":     "Format using basic HTML tags (<h2>, <p>, <ul>, <li>, <code>, <strong>).",
}

EXPERTISE_INSTRUCTIONS = {
    "beginner":     "The user is a beginner. Explain everything from scratch.",
    "intermediate": "The user has basic knowledge of the topic.",
    "expert":       "The user is an expert. Skip basics. Go deep directly.",
}


class CandyUnavailableError(Exception):
    pass

class CandyQuotaError(Exception):
    pass


class CandyClient:
    def __init__(self):
        self._api_url: Optional[str] = None

    def _resolve_url(self, profile) -> str:
        if profile.cache_url and 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().rstrip("/")
        if not link:
            raise CandyUnavailableError("No API link found in candy.json.")
        self._api_url = link
        return self._api_url

    def _build_suffix(self, profile) -> str:
        parts = []
        parts.append(LANG_INSTRUCTIONS.get(profile.lang.upper(), f"Always respond in {profile.lang}."))
        s = STYLE_INSTRUCTIONS.get(profile.style, "")
        if s: parts.append(s)
        t = TONE_INSTRUCTIONS.get(profile.tone, "")
        if t: parts.append(t)
        f = FORMAT_INSTRUCTIONS.get(profile.output_format, "")
        if f: parts.append(f)
        e = EXPERTISE_INSTRUCTIONS.get(profile.expertise, "")
        if e: parts.append(e)
        if not profile.include_examples:
            parts.append("Do not include examples.")
        if profile.safe_mode:
            parts.append("Refuse politely if the request involves harmful or illegal content.")
        if profile.max_tokens < 300:
            parts.append(f"Keep your response under {profile.max_tokens} tokens. Be very brief.")
        elif profile.max_tokens > 2000:
            parts.append(f"You may give a detailed response (up to {profile.max_tokens} tokens).")
        return "\n".join(parts)

    def ask(self, personality: str, prompt: str, profile,
            context: Optional[str] = None, stream: bool = False):
        base = self._resolve_url(profile)
        url = f"{base}/ask/{personality}"
        payload = {
            "prompt": prompt,
            "stream": stream,
            "config": {
                "lang":             profile.lang,
                "max_tokens":       profile.max_tokens,
                "temperature":      profile.temperature,
                "style":            profile.style,
                "output_format":    profile.output_format,
                "tone":             profile.tone,
                "expertise":        profile.expertise,
                "include_examples": profile.include_examples,
                "safe_mode":        profile.safe_mode,
                "system_suffix":    self._build_suffix(profile),
            }
        }
        if context:
            payload["context"] = context
        if stream:
            return self._stream(url, payload, profile)
        return self._post(url, payload, profile)

    def _post(self, url: str, payload: dict, profile) -> dict:
        try:
            resp = httpx.post(url, json=payload, timeout=profile.timeout)
        except httpx.ConnectError:
            self._api_url = None
            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("Unknown personality.")
        resp.raise_for_status()
        return resp.json()

    def _stream(self, url: str, payload: dict, profile) -> Generator[str, None, None]:
        with httpx.stream("POST", url, json=payload, timeout=profile.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, profile) -> dict:
        base = self._resolve_url(profile)
        resp = httpx.get(f"{base}/quota/status", timeout=10)
        resp.raise_for_status()
        return resp.json()

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


_client = CandyClient()
