import os
import json
import logging
import time
import traceback
from typing import List, Dict, Any, Optional, Union, Mapping, cast
from agents.base_agent import BaseAgent
from loaders.ticket_data_loader import TicketDataLoader
from formatters.report_formatter import generate_markdown_report

# Configuration du logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', 
                    filename='orchestrator.log', filemode='w')
logger = logging.getLogger("Orchestrator")

class Orchestrator:
    """
    Orchestrateur pour l'analyse de tickets et la génération de rapports.
    
    Stratégie de gestion des formats:
    - JSON est le format principal pour le traitement des données et l'analyse
    - Markdown est utilisé uniquement comme format de présentation finale
    - Les agents LLM travaillent principalement avec le format JSON
    - La conversion JSON->Markdown se fait uniquement à la fin du processus pour la présentation
    
    Cette approche permet de:
    1. Simplifier le code des agents
    2. Réduire les redondances et incohérences entre formats
    3. Améliorer la performance des agents LLM avec un format plus structuré
    4. Faciliter la maintenance et l'évolution du système
    """
    def __init__(self, 
                 output_dir: str = "output/", 
                 ticket_agent: Optional[BaseAgent] = None,
                 image_sorter: Optional[BaseAgent] = None,
                 image_analyser: Optional[BaseAgent] = None,
                 report_generator: Optional[BaseAgent] = None):
        
        self.output_dir = output_dir
        
        # Assignation directe des agents
        self.ticket_agent = ticket_agent
        self.image_sorter = image_sorter
        self.image_analyser = image_analyser
        self.report_generator = report_generator
        
        # Initialisation du loader de données de ticket
        self.ticket_loader = TicketDataLoader()
        
        # Collecter et enregistrer les informations détaillées sur les agents
        agents_info = self._collecter_info_agents()
        
        logger.info(f"Orchestrator initialisé avec output_dir: {output_dir}")
        logger.info(f"Agents disponibles: TicketAgent={ticket_agent is not None}, ImageSorter={image_sorter is not None}, ImageAnalyser={image_analyser is not None}, ReportGenerator={report_generator is not None}")
        logger.info(f"Configuration des agents: {json.dumps(agents_info, indent=2)}")

    def _collecter_info_agents(self) -> Dict[str, Dict[str, Any]]:
        """
        Collecte des informations détaillées sur les agents configurés
        """
        agents_info = {}
        
        # Information sur l'agent Ticket
        if self.ticket_agent:
            agents_info["ticket_agent"] = self._get_agent_info(self.ticket_agent)
            
        # Information sur l'agent Image Sorter
        if self.image_sorter:
            agents_info["image_sorter"] = self._get_agent_info(self.image_sorter)
            
        # Information sur l'agent Image Analyser
        if self.image_analyser:
            agents_info["image_analyser"] = self._get_agent_info(self.image_analyser)
            
        # Information sur l'agent Report Generator
        if self.report_generator:
            agents_info["report_generator"] = self._get_agent_info(self.report_generator)
            
        return agents_info

    def detecter_tickets(self) -> List[str]:
        """Détecte tous les tickets disponibles dans le répertoire de sortie"""
        logger.info(f"Recherche de tickets dans: {self.output_dir}")
        tickets = []
        
        if not os.path.exists(self.output_dir):
            logger.warning(f"Le répertoire de sortie {self.output_dir} n'existe pas")
            print(f"ERREUR: Le répertoire {self.output_dir} n'existe pas")
            return tickets
            
        for ticket_dir in os.listdir(self.output_dir):
            ticket_path = os.path.join(self.output_dir, ticket_dir)
            if os.path.isdir(ticket_path) and ticket_dir.startswith("ticket_"):
                tickets.append(ticket_path)
        
        logger.info(f"Tickets trouvés: {len(tickets)}")
        print(f"Tickets détectés: {len(tickets)}")
        return tickets

    def lister_tickets(self) -> Dict[int, str]:
        """Liste les tickets disponibles et retourne un dictionnaire {index: chemin}"""
        tickets = self.detecter_tickets()
        ticket_dict = {}
        
        print("\nTickets disponibles:")
        for i, ticket_path in enumerate(tickets, 1):
            ticket_id = os.path.basename(ticket_path)
            ticket_dict[i] = ticket_path
            print(f"{i}. {ticket_id}")
            
        return ticket_dict

    def trouver_rapport(self, extraction_path: str, ticket_id: str) -> Dict[str, Optional[str]]:
        """
        Cherche les rapports disponibles (JSON et/ou MD) pour un ticket
        
        Args:
            extraction_path: Chemin vers l'extraction
            ticket_id: ID du ticket
            
        Returns:
            Dictionnaire avec {"json": chemin_json, "markdown": chemin_md}
        """
        # Utiliser la méthode du TicketDataLoader pour trouver les fichiers
        result = self.ticket_loader.trouver_ticket(extraction_path, ticket_id)
        
        # S'assurer que nous avons un dictionnaire avec la structure correcte
        rapports: Dict[str, Optional[str]] = {"json": None, "markdown": None} if result is None else result
        
        # Si on a un JSON mais pas de Markdown, générer le Markdown à partir du JSON
        json_path = rapports.get("json")
        if json_path and not rapports.get("markdown"):
            logger.info(f"Rapport JSON trouvé sans Markdown correspondant, génération du Markdown: {json_path}")
            
            success, md_path_or_error = generate_markdown_report(json_path)
            if success:
                rapports["markdown"] = md_path_or_error
                logger.info(f"Markdown généré avec succès: {md_path_or_error}")
            else:
                logger.warning(f"Erreur lors de la génération du Markdown: {md_path_or_error}")
        
        return rapports

    def traiter_ticket(self, ticket_path: str) -> bool:
        """Traite un ticket spécifique et retourne True si le traitement a réussi"""
        logger.info(f"Début du traitement du ticket: {ticket_path}")
        print(f"\nTraitement du ticket: {os.path.basename(ticket_path)}")
        
        success = False
        extractions_trouvees = False
        
        if not os.path.exists(ticket_path):
            logger.error(f"Le chemin du ticket n'existe pas: {ticket_path}")
            print(f"ERREUR: Le chemin du ticket n'existe pas: {ticket_path}")
            return False
        
        ticket_id = os.path.basename(ticket_path).replace("ticket_", "")
            
        for extraction in os.listdir(ticket_path):
            extraction_path = os.path.join(ticket_path, extraction)
            if os.path.isdir(extraction_path):
                extractions_trouvees = True
                logger.info(f"Traitement de l'extraction: {extraction}")
                print(f"  Traitement de l'extraction: {extraction}")
                
                # Recherche des rapports (JSON et MD) dans différents emplacements
                rapports = self.trouver_rapport(extraction_path, ticket_id)
                
                # Dossier des pièces jointes
                attachments_dir = os.path.join(extraction_path, "attachments")
                
                # Dossier pour les rapports générés
                rapports_dir = os.path.join(extraction_path, f"{ticket_id}_rapports")
                os.makedirs(rapports_dir, exist_ok=True)
                
                # Préparer les données du ticket à partir des rapports trouvés
                ticket_data = self._preparer_donnees_ticket(rapports, ticket_id)
                
                if ticket_data:
                    success = True
                    logger.info(f"Données du ticket chargées avec succès")
                    print(f"  Données du ticket chargées")
                    
                    # Traitement avec l'agent Ticket
                    if self.ticket_agent:
                        logger.info("Exécution de l'agent Ticket")
                        print("  Analyse du ticket en cours...")
                        
                        # Log détaillé sur l'agent Ticket
                        agent_info = self._get_agent_info(self.ticket_agent)
                        logger.info(f"Agent Ticket: {json.dumps(agent_info, indent=2)}")
                        
                        ticket_analysis = self.ticket_agent.executer(ticket_data)
                        logger.info("Analyse du ticket terminée")
                        print(f"  Analyse du ticket terminée: {len(ticket_analysis) if ticket_analysis else 0} caractères")
                    else:
                        logger.warning("Agent Ticket non disponible")
                        ticket_analysis = None
                        print("  Agent Ticket non disponible, analyse ignorée")

                    # Traitement des images
                    relevant_images = []
                    images_analyses = {}
                    images_count = 0
                    if os.path.exists(attachments_dir):
                        logger.info(f"Vérification des pièces jointes dans: {attachments_dir}")
                        print(f"  Vérification des pièces jointes...")
                        
                        # Log détaillé sur l'agent Image Sorter
                        if self.image_sorter:
                            agent_info = self._get_agent_info(self.image_sorter)
                            logger.info(f"Agent Image Sorter: {json.dumps(agent_info, indent=2)}")
                        
                        # Compter le nombre d'images
                        images = [f for f in os.listdir(attachments_dir) 
                                 if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif'))]
                        images_count = len(images)
                        
                        # Tri des images
                        for attachment in images:
                            attachment_path = os.path.join(attachments_dir, attachment)
                            
                            if self.image_sorter:
                                logger.info(f"Évaluation de la pertinence de l'image: {attachment}")
                                print(f"    Évaluation de l'image: {attachment}")
                                sorting_result = self.image_sorter.executer(attachment_path)
                                is_relevant = sorting_result.get("is_relevant", False)
                                reason = sorting_result.get("reason", "")
                                
                                # Log détaillé du résultat
                                if is_relevant:
                                    logger.info(f"Image {attachment} considérée comme pertinente")
                                else:
                                    logger.info(f"Image {attachment} considérée comme non pertinente")
                                
                                # Ajouter les métadonnées de tri à la liste des analyses
                                images_analyses[attachment_path] = {
                                    "sorting": sorting_result,
                                    "analysis": None  # Sera rempli plus tard si pertinent
                                }
                                
                                if is_relevant:
                                    logger.info(f"Image pertinente identifiée: {attachment} ({reason})")
                                    print(f"    => Pertinente: {reason}")
                                    relevant_images.append(attachment_path)
                                else:
                                    logger.info(f"Image non pertinente: {attachment} ({reason})")
                                    print(f"    => Non pertinente: {reason}")
                            else:
                                logger.warning("Image Sorter non disponible")
                                # Si pas de tri, considérer toutes les images comme pertinentes
                                relevant_images.append(attachment_path)
                                images_analyses[attachment_path] = {
                                    "sorting": {"is_relevant": True, "reason": "Auto-sélectionné (pas de tri)"},
                                    "analysis": None
                                }
                                print(f"    => Auto-sélectionné (pas de tri)")
                        
                        logger.info(f"Images analysées: {images_count}, Images pertinentes: {len(relevant_images)}")
                        print(f"  Images analysées: {images_count}, Images pertinentes: {len(relevant_images)}")
                    else:
                        logger.warning(f"Répertoire des pièces jointes non trouvé: {attachments_dir}")
                        print(f"  Répertoire des pièces jointes non trouvé")

                    # Analyse approfondie des images pertinentes
                    if relevant_images and self.image_analyser:
                        agent_info = self._get_agent_info(self.image_analyser)
                        logger.info(f"Agent Image Analyser: {json.dumps(agent_info, indent=2)}")
                        
                        # S'assurer que l'analyse du ticket est disponible comme contexte
                        contexte_ticket = ticket_analysis if ticket_analysis else "Aucune analyse de ticket disponible"
                        
                        # Analyse de chaque image pertinente
                        for image_path in relevant_images:
                            image_name = os.path.basename(image_path)
                            logger.info(f"Analyse approfondie de l'image: {image_name}")
                            print(f"  Analyse approfondie de l'image: {image_name}")
                            
                            # Appeler l'analyseur d'images avec le contexte du ticket
                            analysis_result = self.image_analyser.executer(image_path, contexte=contexte_ticket)
                            
                            # Vérifier si l'analyse a réussi
                            if "error" in analysis_result and analysis_result["error"]:
                                logger.warning(f"Erreur lors de l'analyse de l'image {image_name}: {analysis_result.get('analyse', 'Erreur inconnue')}")
                                print(f"    => ERREUR: {analysis_result.get('analyse', 'Erreur inconnue')}")
                            else:
                                logger.info(f"Analyse réussie pour l'image {image_name}")
                                print(f"    => Analyse réussie: {len(analysis_result.get('analyse', '')) if 'analyse' in analysis_result else 0} caractères")
                            
                            # Ajouter l'analyse au dictionnaire des analyses d'images
                            if image_path in images_analyses:
                                images_analyses[image_path]["analysis"] = analysis_result
                            else:
                                images_analyses[image_path] = {
                                    "sorting": {"is_relevant": True, "reason": "Auto-sélectionné"},
                                    "analysis": analysis_result
                                }
                            
                            logger.info(f"Analyse complétée pour {image_name}")
                    
                    # Préparer les données pour le rapport final
                    rapport_data = {
                        "ticket_data": ticket_data,
                        "ticket_id": ticket_id,
                        "ticket_analyse": ticket_analysis,  # Utiliser ticket_analyse au lieu de analyse_ticket pour cohérence
                        "analyse_images": images_analyses,
                        "metadata": {
                            "timestamp_debut": self._get_timestamp(),
                            "ticket_id": ticket_id,
                            "images_analysees": images_count,
                            "images_pertinentes": len(relevant_images)
                        }
                    }
                    
                    # Ajout de la clé alternative pour compatibilité
                    rapport_data["analyse_json"] = ticket_analysis
                    
                    if self.report_generator:
                        logger.info("Génération du rapport final")
                        print("  Génération du rapport final")
                        
                        # Log détaillé sur l'agent Report Generator
                        agent_info = self._get_agent_info(self.report_generator)
                        logger.info(f"Agent Report Generator: {json.dumps(agent_info, indent=2)}")
                        
                        # Créer le répertoire pour le rapport si nécessaire
                        rapport_path = os.path.join(rapports_dir, ticket_id)
                        os.makedirs(rapport_path, exist_ok=True)
                        
                        # Générer le rapport
                        json_path, md_path = self.report_generator.executer(rapport_data, rapport_path)
                        
                        if json_path:
                            logger.info(f"Rapport JSON généré à: {rapport_path}")
                            print(f"  Rapport JSON généré avec succès: {os.path.basename(json_path)}")
                            
                            # Utiliser directement le rapport Markdown généré par l'agent
                            if md_path:
                                logger.info(f"Rapport Markdown généré à: {rapport_path}")
                                print(f"  Rapport Markdown généré avec succès: {os.path.basename(md_path)}")
                                # Vérifier si le rapport Markdown contient un tableau des échanges
                                with open(md_path, "r", encoding="utf-8") as f:
                                    md_content = f.read()
                                    has_exchanges = "| Date | Émetteur |" in md_content
                                    logger.info(f"Vérification du rapport Markdown: Tableau des échanges {'présent' if has_exchanges else 'absent'}")
                            else:
                                logger.warning(f"Erreur lors de la génération du rapport Markdown")
                                print(f"  ERREUR: Problème lors de la génération du rapport Markdown")
                        else:
                            logger.warning("Erreur lors de la génération du rapport JSON")
                            print(f"  ERREUR: Problème lors de la génération du rapport JSON")
                    else:
                        logger.warning("Report Generator non disponible")
                        print("  Report Generator non disponible, génération de rapport ignorée")

                    print(f"Traitement du ticket {os.path.basename(ticket_path)} terminé avec succès.\n")
                    logger.info(f"Traitement du ticket {ticket_path} terminé avec succès.")
                else:
                    logger.warning(f"Aucune donnée de ticket trouvée pour: {ticket_id}")
                    print(f"  ERREUR: Aucune donnée de ticket trouvée pour {ticket_id}")
        
        if not extractions_trouvees:
            logger.warning(f"Aucune extraction trouvée dans le ticket: {ticket_path}")
            print(f"  ERREUR: Aucune extraction trouvée dans le ticket")
            
        return success
        
    def _preparer_donnees_ticket(self, rapports: Dict[str, Optional[str]], ticket_id: str) -> Optional[Dict]:
        """
        Prépare les données du ticket à partir des rapports trouvés (JSON et/ou MD)
        
        Args:
            rapports: Dictionnaire avec les chemins des rapports JSON et MD
            ticket_id: ID du ticket
            
        Returns:
            Dictionnaire avec les données du ticket, ou None si aucun rapport n'est trouvé
        """
        ticket_data = None
        
        # Si aucun rapport n'est trouvé
        if not rapports or (not rapports.get("json") and not rapports.get("markdown")):
            logger.warning(f"Aucun rapport trouvé pour le ticket {ticket_id}")
            return None
        
        # Privilégier le format JSON (format principal)
        if rapports.get("json") and rapports["json"] is not None:
            try:
                ticket_data = self.ticket_loader.charger(rapports["json"])
                logger.info(f"Données JSON chargées depuis: {rapports['json']}")
                print(f"  Rapport JSON chargé: {os.path.basename(rapports['json'])}")
                # Ajouter une métadonnée sur le format source
                if ticket_data and "metadata" not in ticket_data:
                    ticket_data["metadata"] = {}
                if ticket_data:
                    ticket_data["metadata"]["format_source"] = "json"
            except Exception as e:
                logger.error(f"Erreur lors du chargement du JSON: {e}")
                print(f"  ERREUR: Impossible de charger le fichier JSON: {e}")
        
        # Fallback sur le Markdown uniquement si JSON non disponible
        if not ticket_data and rapports.get("markdown") and rapports["markdown"] is not None:
            try:
                # Utiliser le loader pour charger les données depuis le Markdown
                ticket_data = self.ticket_loader.charger(rapports["markdown"])
                logger.info(f"Données Markdown chargées depuis: {rapports['markdown']} (fallback)")
                print(f"  Rapport Markdown chargé (fallback): {os.path.basename(rapports['markdown'])}")
                # Ajouter une métadonnée sur le format source
                if ticket_data and "metadata" not in ticket_data:
                    ticket_data["metadata"] = {}
                if ticket_data:
                    ticket_data["metadata"]["format_source"] = "markdown"
            except Exception as e:
                logger.error(f"Erreur lors du chargement du Markdown: {e}")
                print(f"  ERREUR: Impossible de charger le fichier Markdown: {e}")
        
        # Assurer que l'ID du ticket est correct
        if ticket_data:
            ticket_data["code"] = ticket_id
        
        return ticket_data

    def executer(self, ticket_specifique: Optional[str] = None):
        """
        Exécute l'orchestrateur soit sur un ticket spécifique, soit permet de choisir
        
        Args:
            ticket_specifique: Chemin du ticket spécifique à traiter (optionnel)
        """
        start_time = time.time()
        
        # Stocker le ticket spécifique
        self.ticket_specifique = ticket_specifique
        
        # Obtenir la liste des tickets
        if ticket_specifique:
            # Utiliser juste le ticket spécifique
            ticket_dirs = self.detecter_tickets()
            ticket_dirs = [t for t in ticket_dirs if t.endswith(ticket_specifique)]
            logger.info(f"Ticket spécifique à traiter: {ticket_specifique}")
        else:
            # Lister tous les tickets
            ticket_dirs = self.detecter_tickets()
            logger.info(f"Tickets à traiter: {len(ticket_dirs)}")
        
        if not ticket_dirs:
            logger.warning("Aucun ticket trouvé dans le répertoire de sortie")
            return
        
        # Un seul log de début d'exécution
        logger.info("Début de l'exécution de l'orchestrateur")
        print("Début de l'exécution de l'orchestrateur")
        
        # Traitement des tickets
        for ticket_dir in ticket_dirs:
            if ticket_specifique and not ticket_dir.endswith(ticket_specifique):
                continue
                
            try:
                self.traiter_ticket(ticket_dir)
            except Exception as e:
                logger.error(f"Erreur lors du traitement du ticket {ticket_dir}: {str(e)}")
                print(f"Erreur lors du traitement du ticket {ticket_dir}: {str(e)}")
                traceback.print_exc()
        
        # Calcul de la durée d'exécution
        duration = time.time() - start_time
        logger.info(f"Fin de l'exécution de l'orchestrateur (durée: {duration:.2f} secondes)")
        print(f"Fin de l'exécution de l'orchestrateur (durée: {duration:.2f} secondes)")

    def _get_timestamp(self) -> str:
        """Retourne un timestamp au format YYYYMMDD_HHMMSS"""
        from datetime import datetime
        return datetime.now().strftime("%Y%m%d_%H%M%S")

    def _get_agent_info(self, agent: Optional[BaseAgent]) -> Dict:
        """
        Récupère les informations détaillées sur un agent.
        """
        if not agent:
            return {"status": "non configuré"}
        
        # Récupérer les informations du modèle
        model_info = {
            "nom": agent.nom,
            "model": getattr(agent.llm, "modele", str(type(agent.llm))),
        }
        
        # Ajouter les paramètres de configuration s'ils sont disponibles directement dans l'agent
        # Utiliser getattr avec une valeur par défaut pour éviter les erreurs
        model_info["temperature"] = getattr(agent, "temperature", None)
        model_info["top_p"] = getattr(agent, "top_p", None)
        model_info["max_tokens"] = getattr(agent, "max_tokens", None)
        
        # Ajouter le prompt système s'il est disponible
        if hasattr(agent, "system_prompt"):
            prompt_preview = getattr(agent, "system_prompt", "")
            # Tronquer le prompt s'il est trop long
            if prompt_preview and len(prompt_preview) > 200:
                prompt_preview = prompt_preview[:200] + "..." 
            model_info["system_prompt_preview"] = prompt_preview
        
        # Supprimer les valeurs None
        model_info = {k: v for k, v in model_info.items() if v is not None}
        
        return model_info