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

Usage :
    from cedric_ai import CedricAI
    ai = CedricAI()
    print(ai.ask("Bonjour !"))
"""

import requests
import json
import time
import uuid
import os
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


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 interroger l'IA Cédric.
    500 requêtes gratuites par jour par adresse IP.
    File d'attente automatique si le serveur est hors ligne.
    """

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

    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

    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)

    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,
            "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) -> Optional[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) -> str:
        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()

    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:
            data = r.json()
            raise PermissionError(data.get("message", "Quota de 500 requêtes/jour 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:
                        result = self._send(job["prompt"])
                        job["status"]  = "done"
                        job["result"]  = result
                        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)
