import abc
import json
import time
import requests
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional, Union

class LLM(abc.ABC):
    """Classe abstraite de base pour tous les modèles LLM"""
    
    def __init__(self):
        """Initialisation des attributs communs"""
        # Attributs publics
        self.Modele: str = ""
        self.prompt_system: str = ""
        self.o_temperature: float = 0.8
        self.o_top_k: int = 40
        self.o_top_p: float = 0.9
        
        # Constantes publiques
        self.dureeTraitement: timedelta = timedelta()
        self.reponseErreur: bool = False
        
        # Attributs protégés
        self._Contenu: Dict[str, Any] = {}
        self._m_sUrlFonction: str = ""
        self._stream: bool = False
        self._m_sFormat: str = ""
        self._heureDepart: Optional[datetime] = None
        self._heureFin: Optional[datetime] = None
        
        # Initialisation par défaut
        self._stream = False
    
    @property
    def urlFonction(self) -> str:
        """Getter pour l'URL de la fonction"""
        return self._m_sUrlFonction
    
    @urlFonction.setter
    def urlFonction(self, valeur: str):
        """Setter pour l'URL de la fonction"""
        self._m_sUrlFonction = valeur
    
    @property
    def format(self) -> str:
        """Getter pour le format"""
        return self._m_sFormat
    
    @format.setter
    def format(self, valeur: str):
        """Setter pour le format"""
        self._m_sFormat = valeur
    
    def model_list(self) -> List[str]:
        """Retourne la liste des modèles disponibles"""
        return []
    
    def commandeAutorisation(self) -> str:
        """Retourne la commande d'autorisation à utiliser pour les API"""
        return "Bearer"
    
    def Interroger(self, question: str) -> str:
        """Interroge le LLM avec une question et retourne la réponse"""
        response = self.LLM_POST(question)
        return response
    
    def LLM_POST(self, question: str) -> str:
        """
        Méthode interne pour communiquer avec l'API du LLM
        Cette méthode gère les appels HTTP et le formattage de la réponse
        """
        erreur = ""
        reponse = ""
        
        # Préparation de la requête
        url = self.urlBase() + self.urlFonction
        headers = {
            "Content-Type": "application/json"
        }
        
        # Ajout de l'autorisation si nécessaire
        if self.cleAPI() != "":
            headers["Authorization"] = f"{self.commandeAutorisation()} {self.cleAPI()}"
        
        # Préparation du contenu
        self._Contenu = {}
        if self.Modele != "":
            self._Contenu["model"] = self.Modele
        
        # Préparation spécifique à la classe fille
        self._even_LLM_POST(question)
        
        # Envoi de la requête
        self._heureDepart = datetime.now()
        
        try:
            # Timeout de 2 minutes (équivalent à duréeNonRéponse = 2min)
            response = requests.post(
                url=url,
                headers=headers,
                json=self._Contenu,
                timeout=120
            )
            
            self._heureFin = datetime.now()
            self.dureeTraitement = self._heureFin - self._heureDepart
            
            # Traitement de la réponse
            if response.status_code in [200, 201]:
                reponse = self._interrogerRetourneReponse(response.text)
                self.reponseErreur = False
                return reponse
            else:
                erreur = response.text
                self.reponseErreur = True
                return erreur
                
        except Exception as e:
            self._heureFin = datetime.now()
            self.dureeTraitement = self._heureFin - self._heureDepart
            self.reponseErreur = True
            return str(e)
    
    @abc.abstractmethod
    def urlBase(self) -> str:
        """Retourne l'URL de base de l'API (à implémenter dans les classes filles)"""
        pass
    
    @abc.abstractmethod
    def cleAPI(self) -> str:
        """Retourne la clé API à utiliser (à implémenter dans les classes filles)"""
        pass
    
    @abc.abstractmethod
    def _even_LLM_POST(self, question: str) -> None:
        """
        Méthode appelée lors de la préparation de la requête POST
        Permet de personnaliser le contenu pour chaque type de LLM
        """
        pass
    
    @abc.abstractmethod
    def _interrogerRetourneReponse(self, reponse: str) -> str:
        """
        Méthode pour extraire la réponse du LLM à partir du JSON retourné
        À implémenter selon le format de réponse spécifique à chaque LLM
        """
        pass 