"""
CedricAI v7 — Client Python pour l'IA Cédric
=============================================
pip install cedric-ai

Usage :
    from cedric_ai import CedricAI
    ai = CedricAI()
    print(ai.ask("Bonjour !"))
    ai.generate_image("un chat dans l'espace", style="anime")
"""

import requests
import json
import time
import uuid
import os
import base64
import threading
from datetime import datetime
from typing import Optional, Callable

_CONFIG_URL = "https://client.hubworld.net/ia.json"
_cached_url = None
_cache_time = 0
_CACHE_TTL  = 60

IMAGE_STYLES = [
    "normal", "anime", "realistic", "cartoon",
    "painting", "cyberpunk", "sketch", "watercolor",
    "pixel", "fantasy"
]


def _get_server_url() -> str:
    global _cached_url, _cache_time
    now = time.time()
    if _cached_url and (now - _cache_time) < _CACHE_TTL:
        return _cached_url
    try:
        r = requests.get(_CONFIG_URL, timeout=5)
        r.raise_for_status()
        _cached_url = r.json().get("link", "").rstrip("/")
        _cache_time = now
        return _cached_url
    except Exception:
        if _cached_url:
            return _cached_url
        raise ConnectionError("Impossible de récupérer la configuration du serveur Cédric.")


class CedricAI:
    """
    Client pour l'IA Cédric.
    500 requêtes gratuites par jour par IP.
    Génération d'images incluse.
    """

    def __init__(
        self,
        queue_mode: bool = True,
        queue_file: str = "cedric_queue.json",
        retry_interval: int = 30,
        timeout: int = 120,
        image_timeout: int = 180,
    ):
        self.queue_mode     = queue_mode
        self.queue_file     = queue_file
        self.retry_interval = retry_interval
        self.timeout        = timeout
        self.image_timeout  = image_timeout
        self._queue         = self._load_queue()
        self._callbacks     = {}
        self._worker_running = False
        if queue_mode:
            self._start_worker()

    # ─────────────────────────────────────────────
    # 📡 STATUT
    # ─────────────────────────────────────────────

    def is_online(self) -> bool:
        try:
            url = _get_server_url()
            r = requests.get(f"{url}/api/status", timeout=5)
            return r.status_code == 200
        except Exception:
            return False

    # ─────────────────────────────────────────────
    # 💬 CHAT
    # ─────────────────────────────────────────────

    def ask(
        self,
        prompt: str,
        system: Optional[str] = None,
        wait_if_offline: bool = True,
        max_wait: int = 600,
    ) -> str:
        full_prompt = self._build_prompt(prompt, system)
        if not wait_if_offline:
            if not self.is_online():
                raise ConnectionError("Serveur Cédric indisponible.")
            return self._send(full_prompt)
        waited = 0
        while not self.is_online():
            if waited >= max_wait:
                raise TimeoutError(f"Serveur indisponible après {max_wait}s")
            print(f"⏳ Indisponible, retry dans {self.retry_interval}s...")
            time.sleep(self.retry_interval)
            waited += self.retry_interval
        return self._send(full_prompt)

    # ─────────────────────────────────────────────
    # 🎨 GÉNÉRATION D'IMAGE
    # ─────────────────────────────────────────────

    def generate_image(
        self,
        prompt: str,
        style: str = "normal",
        save_path: Optional[str] = None,
        wait_if_offline: bool = True,
        max_wait: int = 600,
    ) -> bytes:
        """
        Génère une image depuis un prompt texte.

        Args:
            prompt:          Description de l'image
            style:           Style visuel (voir IMAGE_STYLES)
            save_path:       Chemin pour sauvegarder l'image (ex: "image.png")
            wait_if_offline: Attend si serveur hors ligne
            max_wait:        Temps max d'attente

        Returns:
            bytes — données PNG de l'image

        Raises:
            ValueError:      Si le style est invalide
            ConnectionError: Si serveur indisponible
            PermissionError: Si quota atteint
        """
        if style not in IMAGE_STYLES:
            raise ValueError(f"Style invalide. Disponibles : {', '.join(IMAGE_STYLES)}")

        if wait_if_offline:
            waited = 0
            while not self.is_online():
                if waited >= max_wait:
                    raise TimeoutError(f"Serveur indisponible après {max_wait}s")
                print(f"⏳ Indisponible, retry dans {self.retry_interval}s...")
                time.sleep(self.retry_interval)
                waited += self.retry_interval
        elif not self.is_online():
            raise ConnectionError("Serveur Cédric indisponible.")

        url = _get_server_url()
        r = requests.post(
            f"{url}/api/image",
            json={"prompt": prompt, "style": style},
            timeout=self.image_timeout,
        )
        if r.status_code == 429:
            raise PermissionError(r.json().get("message", "Quota atteint."))
        if r.status_code == 503:
            raise ConnectionError("Serveur d'images hors ligne.")
        r.raise_for_status()

        img_b64 = r.json().get("image")
        if not img_b64:
            raise RuntimeError("Réponse image invalide")

        img_bytes = base64.b64decode(img_b64)

        if save_path:
            with open(save_path, "wb") as f:
                f.write(img_bytes)
            print(f"✅ Image sauvegardée : {save_path}")

        return img_bytes

    def generate_image_async(
        self,
        prompt: str,
        style: str = "normal",
        save_path: Optional[str] = None,
        callback: Optional[Callable] = None,
    ) -> str:
        """
        Génère une image de façon asynchrone.

        Returns:
            job_id — récupérer avec wait_for_result()
        """
        job_id = str(uuid.uuid4())[:8]
        job = {
            "id":         job_id,
            "type":       "image",
            "prompt":     prompt,
            "style":      style,
            "save_path":  save_path,
            "status":     "pending",
            "result":     None,
            "error":      None,
            "created_at": datetime.now().isoformat(),
            "done_at":    None,
        }
        self._queue[job_id] = job
        self._save_queue()
        if callback:
            self._callbacks[job_id] = callback
        print(f"📬 Job image {job_id} en file d'attente")
        return job_id

    # ─────────────────────────────────────────────
    # 📬 ASYNC GÉNÉRAL
    # ─────────────────────────────────────────────

    def ask_async(
        self,
        prompt: str,
        system: Optional[str] = None,
        callback: Optional[Callable] = None,
    ) -> str:
        job_id = str(uuid.uuid4())[:8]
        job = {
            "id":         job_id,
            "type":       "chat",
            "prompt":     self._build_prompt(prompt, system),
            "status":     "pending",
            "result":     None,
            "error":      None,
            "created_at": datetime.now().isoformat(),
            "done_at":    None,
        }
        self._queue[job_id] = job
        self._save_queue()
        if callback:
            self._callbacks[job_id] = callback
        print(f"📬 Job {job_id} en file d'attente")
        return job_id

    def get_result(self, job_id: str):
        job = self._queue.get(job_id)
        if not job:
            raise KeyError(f"Job {job_id} introuvable")
        if job["status"] == "error":
            raise RuntimeError(f"Job {job_id} échoué : {job['error']}")
        if job["status"] == "done":
            return job["result"]
        return None

    def wait_for_result(self, job_id: str, timeout: int = 600):
        start = time.time()
        while time.time() - start < timeout:
            result = self.get_result(job_id)
            if result is not None:
                return result
            time.sleep(2)
        raise TimeoutError(f"Job {job_id} pas terminé après {timeout}s")

    def quota(self) -> dict:
        try:
            url = _get_server_url()
            r = requests.get(f"{url}/api/quota", timeout=5)
            r.raise_for_status()
            return r.json()
        except Exception as e:
            return {"error": str(e)}

    def queue_status(self) -> dict:
        return {
            "online":  self.is_online(),
            "pending": sum(1 for j in self._queue.values() if j["status"] == "pending"),
            "done":    sum(1 for j in self._queue.values() if j["status"] == "done"),
            "errors":  sum(1 for j in self._queue.values() if j["status"] == "error"),
            "total":   len(self._queue),
        }

    def clear_done(self):
        self._queue = {k: v for k, v in self._queue.items() if v["status"] != "done"}
        self._save_queue()

    # ─────────────────────────────────────────────
    # 🔧 INTERNE
    # ─────────────────────────────────────────────

    def _build_prompt(self, prompt: str, system: Optional[str]) -> str:
        if system:
            return f"{system}\n\nUser : {prompt}\nAssistant :"
        return prompt

    def _send(self, prompt: str) -> str:
        url = _get_server_url()
        r = requests.post(
            f"{url}/api/ask",
            json={"prompt": prompt},
            timeout=self.timeout,
        )
        if r.status_code == 429:
            raise PermissionError(r.json().get("message", "Quota atteint."))
        r.raise_for_status()
        return r.json()["response"].strip()

    def _load_queue(self) -> dict:
        try:
            if os.path.exists(self.queue_file):
                with open(self.queue_file, "r", encoding="utf-8") as f:
                    return json.load(f)
        except Exception:
            pass
        return {}

    def _save_queue(self):
        try:
            with open(self.queue_file, "w", encoding="utf-8") as f:
                json.dump(self._queue, f, indent=2, ensure_ascii=False)
        except Exception as e:
            print(f"⚠️ Erreur queue : {e}")

    def _start_worker(self):
        if self._worker_running:
            return
        self._worker_running = True
        t = threading.Thread(target=self._worker_loop, daemon=True)
        t.start()

    def _worker_loop(self):
        while True:
            pending = [j for j in self._queue.values() if j["status"] == "pending"]
            if pending and self.is_online():
                for job in pending:
                    try:
                        if job.get("type") == "image":
                            result = self.generate_image(
                                job["prompt"],
                                style=job.get("style", "normal"),
                                save_path=job.get("save_path"),
                                wait_if_offline=False,
                            )
                        else:
                            result = self._send(job["prompt"])
                        job["status"]  = "done"
                        job["result"]  = result if isinstance(result, str) else "[image bytes]"
                        job["done_at"] = datetime.now().isoformat()
                        self._save_queue()
                        cb = self._callbacks.get(job["id"])
                        if cb:
                            try: cb(job["id"], result)
                            except Exception: pass
                    except Exception as e:
                        job["status"] = "error"
                        job["error"]  = str(e)
                        self._save_queue()
            time.sleep(self.retry_interval)
