"""
candy.modules.base — Classe de base v6 (v20)
=============================================
Nouveautés v20 :
  - ask_async() / stream_async() / batch_async() sur tous les modules
  - use_cache=True sur ask() et ask_async()
"""

import sys
from typing import Optional, Generator, AsyncGenerator
from ..client import _client
from ..config import get_profile, Profile


class BoundModule:
    """Module lié à un profil via .use('nom')."""

    def __init__(self, personality: str, profile: Profile):
        self._personality = personality
        self._profile = profile

    # ── Sync ──────────────────────────────────────────────────────────────────

    def ask(self, prompt: str, context: Optional[str] = None,
            use_cache: bool = False) -> str:
        result = _client.ask(self._personality, prompt, self._profile,
                             context=context, stream=False, use_cache=use_cache)
        return result.get("response", "")

    def stream(self, prompt: str, context: Optional[str] = None) -> Generator[str, None, None]:
        yield from _client.ask(self._personality, prompt, self._profile,
                                context=context, stream=True)

    def stream_print(self, prompt: str, context: Optional[str] = None, end: str = "\n") -> str:
        full = []
        for token in self.stream(prompt, context=context):
            sys.stdout.write(token)
            sys.stdout.flush()
            full.append(token)
        sys.stdout.write(end)
        return "".join(full)

    def ask_with_meta(self, prompt: str, context: Optional[str] = None) -> dict:
        return _client.ask(self._personality, prompt, self._profile,
                           context=context, stream=False)

    def batch(self, prompts: list, context: Optional[str] = None) -> list:
        return _client.batch(self._personality, prompts, self._profile, context=context)

    # ── Async (v20) ───────────────────────────────────────────────────────────

    async def ask_async(self, prompt: str, context: Optional[str] = None,
                        use_cache: bool = False) -> str:
        return await _client.ask_async(self._personality, prompt, self._profile,
                                       context=context, use_cache=use_cache)

    async def stream_async(self, prompt: str,
                           context: Optional[str] = None) -> AsyncGenerator[str, None]:
        async for token in _client.stream_async(self._personality, prompt,
                                                 self._profile, context=context):
            yield token

    async def batch_async(self, prompts: list, context: Optional[str] = None) -> list:
        return await _client.batch_async(self._personality, prompts,
                                          self._profile, context=context)

    # ── Utilitaires ───────────────────────────────────────────────────────────

    def quota(self) -> dict:
        return _client.quota(self._profile)

    def ping(self) -> float:
        return _client.ping(self._profile)

    def is_online(self) -> bool:
        return _client.is_online(self._profile)

    def chat(self, max_history: int = 20):
        from ..conversation import Conversation
        name = object.__getattribute__(self._profile, "_name")
        class _FakeModule:
            _personality = self._personality
        return Conversation(_FakeModule, profile=name, max_history=max_history)

    def __repr__(self):
        name = object.__getattribute__(self._profile, "_name")
        return f"<candy.{self._personality} profile='{name}'>"


class BaseModule:
    """Classe de base pour toutes les personnalités candy."""

    _personality: str = "full"

    @classmethod
    def use(cls, profile_name: str) -> BoundModule:
        """
        Utilise un profil spécifique.

            cfg.A.lang = "FR"
            Coding.use("A").ask("Explique les décorateurs")
        """
        return BoundModule(cls._personality, get_profile(profile_name))

    # ── Sync ──────────────────────────────────────────────────────────────────

    @classmethod
    def ask(cls, prompt: str, context: Optional[str] = None,
            use_cache: bool = False) -> str:
        """Appel simple avec le profil 'default'."""
        result = _client.ask(cls._personality, prompt, get_profile("default"),
                             context=context, stream=False, use_cache=use_cache)
        return result.get("response", "")

    @classmethod
    def stream(cls, prompt: str, context: Optional[str] = None) -> Generator[str, None, None]:
        """Streaming avec le profil 'default'."""
        yield from _client.ask(cls._personality, prompt, get_profile("default"),
                                context=context, stream=True)

    @classmethod
    def stream_print(cls, prompt: str, context: Optional[str] = None, end: str = "\n") -> str:
        """Streame et affiche. Retourne la réponse complète."""
        full = []
        for token in cls.stream(prompt, context=context):
            sys.stdout.write(token)
            sys.stdout.flush()
            full.append(token)
        sys.stdout.write(end)
        return "".join(full)

    @classmethod
    def ask_with_meta(cls, prompt: str, context: Optional[str] = None) -> dict:
        return _client.ask(cls._personality, prompt, get_profile("default"),
                           context=context, stream=False)

    @classmethod
    def batch(cls, prompts: list, context: Optional[str] = None) -> list:
        """Envoie plusieurs prompts d'un coup. Retourne une liste de réponses."""
        return _client.batch(cls._personality, prompts, get_profile("default"), context=context)

    @classmethod
    def chat(cls, profile: str = "default", max_history: int = 20):
        """
        Ouvre une session de conversation multi-tours.

            session = Coding.chat(profile="A")
            print(session.say("Explique les générateurs"))
            print(session.say("Donne un exemple"))
        """
        from ..conversation import Conversation
        return Conversation(cls, profile=profile, max_history=max_history)

    # ── Async (v20) ───────────────────────────────────────────────────────────

    @classmethod
    async def ask_async(cls, prompt: str, context: Optional[str] = None,
                        use_cache: bool = False) -> str:
        """
        Version async de ask().

            import asyncio
            result = asyncio.run(Coding.ask_async("Write a bubble sort"))
        """
        return await _client.ask_async(cls._personality, prompt, get_profile("default"),
                                       context=context, use_cache=use_cache)

    @classmethod
    async def stream_async(cls, prompt: str,
                           context: Optional[str] = None) -> AsyncGenerator[str, None]:
        """
        Streaming async.

            async for token in Coding.stream_async("Write a sort"):
                print(token, end="", flush=True)
        """
        async for token in _client.stream_async(cls._personality, prompt,
                                                  get_profile("default"), context=context):
            yield token

    @classmethod
    async def batch_async(cls, prompts: list, context: Optional[str] = None) -> list:
        """
        Batch async — toutes les requêtes en parallèle.

            import asyncio
            results = asyncio.run(Coding.batch_async(["sort", "reverse", "search"]))
        """
        return await _client.batch_async(cls._personality, prompts,
                                          get_profile("default"), context=context)

    # ── Utilitaires ───────────────────────────────────────────────────────────

    @classmethod
    def quota(cls) -> dict:
        return _client.quota(get_profile("default"))

    @classmethod
    def ping(cls) -> float:
        return _client.ping(get_profile("default"))

    @classmethod
    def is_online(cls) -> bool:
        return _client.is_online(get_profile("default"))

    @classmethod
    def personalities(cls) -> list:
        return _client.personalities(get_profile("default"))
