import json
import base64
import os
from typing import Dict, List, Any, Optional, Union, Tuple
from datetime import timedelta
import requests
from .llm import LLM

class LlamaVision(LLM):
    """
    Classe pour l'intégration avec le modèle Llama Vision 3.2 90B
    Cette classe hérite de la classe de base LLM et ajoute le support des images
    """
    
    def __init__(self):
        """
        Initialisation des attributs spécifiques à Llama Vision
        """
        super().__init__()
        
        # Configuration du modèle
        self.Modele = "llama-vision:3.2-90b"
        
        # Attributs spécifiques pour Llama Vision
        self.image_path: str = ""
        self.image_data: Optional[bytes] = None
        self.json_data: Dict[str, Any] = {}
        
        # Paramètres de génération
        self.o_mirostat: int = 0
        self.o_mirostat_eta: float = 0.1
        self.o_mirostat_tau: float = 5
        self.o_num_ctx: int = 4096  # Contexte plus large pour les images + texte
        self.o_repeat_last_n: int = 64
        self.o_repeat_penalty: float = 1.1
        self.o_temperature: float = 0.7  # Valeur par défaut adaptée pour les descriptions d'images
        self.o_seed: int = 0
        self.o_stop: List[str] = []
        self.o_num_predict: int = 1024  # Nombre de tokens à prédire
        self.o_top_k: int = 40
        self.o_top_p: float = 0.9
        self.o_min_p: float = 0
        
        # Options supplémentaires
        self.keep_alive: timedelta = timedelta(minutes=5)
        self.raw: bool = False
        self._stream: bool = False
        
        # Stockage des résultats
        self.dernier_resultat: Dict[str, Any] = {}
        self.historique_resultats: List[Dict[str, Any]] = []
    
    def urlBase(self) -> str:
        """
        Retourne l'URL de base de l'API Ollama pour Llama Vision
        """
        return "http:/217.182.105.173/:11434/"
    
    def cleAPI(self) -> str:
        """
        Retourne la clé API (vide pour Ollama local)
        """
        return ""
    
    def set_image(self, image_path: str) -> bool:
        """
        Définit l'image à analyser à partir d'un chemin de fichier
        Retourne True si l'image a été chargée avec succès
        """
        if not os.path.exists(image_path):
            return False
        
        try:
            with open(image_path, "rb") as f:
                self.image_data = f.read()
            self.image_path = image_path
            return True
        except Exception as e:
            print(f"Erreur lors du chargement de l'image: {e}")
            return False
    
    def set_image_data(self, image_data: bytes) -> bool:
        """
        Définit directement les données de l'image à analyser
        """
        try:
            self.image_data = image_data
            self.image_path = ""
            return True
        except Exception as e:
            print(f"Erreur lors de la définition des données d'image: {e}")
            return False
    
    def set_json_data(self, json_data: Dict[str, Any]) -> bool:
        """
        Définit les données JSON à associer à l'image
        """
        try:
            self.json_data = json_data
            return True
        except Exception as e:
            print(f"Erreur lors de la définition des données JSON: {e}")
            return False
    
    def Interroger(self, question: str) -> str:
        """
        Interroge le modèle Llama Vision avec une question et l'image chargée
        """
        if not self.image_data:
            return "Erreur: Aucune image n'a été chargée. Utilisez set_image() ou set_image_data() d'abord."
        
        return self.LLM_POST(question)
    
    def _even_LLM_POST(self, question: str) -> None:
        """
        Préparation du contenu de la requête pour Llama Vision
        """
        # Paramètres de base
        self._Contenu["model"] = self.Modele
        self._Contenu["system"] = self.prompt_system if self.prompt_system else "Tu es un assistant d'analyse d'images qui peut analyser des images et répondre à des questions en français. Sois précis et détaillé dans tes descriptions."
        self._Contenu["prompt"] = question
        self._Contenu["stream"] = self._stream
        self._Contenu["raw"] = self.raw
        
        # Ajout de l'image en base64
        if self.image_data:
            base64_image = base64.b64encode(self.image_data).decode('utf-8')
            self._Contenu["images"] = [base64_image]
        
        # Conversion de timedelta en nombre de secondes pour keep_alive
        if isinstance(self.keep_alive, timedelta):
            self._Contenu["keep_alive"] = int(self.keep_alive.total_seconds())
        
        # Ajout des données JSON dans le prompt si disponibles
        if self.json_data:
            json_str = json.dumps(self.json_data, ensure_ascii=False, indent=2)
            # On ajoute le JSON au prompt pour qu'il soit traité avec l'image
            self._Contenu["prompt"] = f"{question}\n\nVoici des données JSON associées à cette image:\n```json\n{json_str}\n```"
        
        # Options avancées pour la génération
        self._Contenu["options"] = {
            "mirostat": self.o_mirostat,
            "mirostat_eta": self.o_mirostat_eta,
            "mirostat_tau": self.o_mirostat_tau,
            "num_ctx": self.o_num_ctx,
            "repeat_last_n": self.o_repeat_last_n,
            "repeat_penalty": self.o_repeat_penalty,
            "temperature": self.o_temperature,
            "seed": self.o_seed,
            "stop": self.o_stop,
            "num_predict": self.o_num_predict,
            "top_k": self.o_top_k,
            "top_p": self.o_top_p,
            "min_p": self.o_min_p
        }
    
    def _interrogerRetourneReponse(self, reponse: str) -> str:
        """
        Extraction de la réponse à partir du JSON retourné par Llama Vision
        """
        try:
            data = json.loads(reponse)
            self.dernier_resultat = data
            self.historique_resultats.append(data)
            
            # Ollama retourne généralement la réponse dans la clé "response"
            if "response" in data:
                return data["response"]
            else:
                return str(data)
        except Exception as e:
            return f"Erreur lors de l'analyse de la réponse: {e}\n\nRéponse brute: {reponse}"
    
    def fusionner_json_avec_resultats(self) -> Dict[str, Any]:
        """
        Fusionne les données JSON existantes avec les résultats de l'analyse d'image
        """
        if not self.dernier_resultat:
            return self.json_data
        
        # Créer une copie du JSON original
        resultat_fusionne = self.json_data.copy() if self.json_data else {}
        
        # Ajouter le résultat de l'analyse d'image
        if "analyse_image" not in resultat_fusionne:
            resultat_fusionne["analyse_image"] = []
        
        # Ajouter le résultat à la liste des analyses
        nouvelle_analyse = {
            "modele": self.Modele,
            "reponse": self.dernier_resultat.get("response", ""),
            "parametres": {
                "temperature": self.o_temperature,
                "top_p": self.o_top_p,
                "num_ctx": self.o_num_ctx
            }
        }
        
        resultat_fusionne["analyse_image"].append(nouvelle_analyse)
        
        return resultat_fusionne
    
    def sauvegarder_resultats(self, chemin_fichier: str) -> bool:
        """
        Sauvegarde les résultats fusionnés dans un fichier JSON
        """
        try:
            resultats_fusionnes = self.fusionner_json_avec_resultats()
            
            with open(chemin_fichier, 'w', encoding='utf-8') as f:
                json.dump(resultats_fusionnes, f, ensure_ascii=False, indent=2)
            
            return True
        except Exception as e:
            print(f"Erreur lors de la sauvegarde des résultats: {e}")
            return False 