"""
Gestionnaire de suggestions pour le Mode Assisté.
Gère les suggestions en temps réel, les scores de confiance et les timeouts.
"""

import time
from typing import Dict, Any, Optional, Callable, List
from datetime import datetime, timedelta
from threading import Lock

from .learning_manager import LearningManager
from .embeddings_manager import EmbeddingsManager
from .logger import Logger
from .workflow_matcher import WorkflowMatcher, WorkflowMatch


class SuggestionManager:
    """
    Gestionnaire de suggestions pour le Mode Assisté.
    """
    
    def __init__(
        self,
        learning_manager: LearningManager,
        embeddings_manager: EmbeddingsManager,
        logger: Logger,
        config: Dict[str, Any],
        workflow_matcher: Optional[WorkflowMatcher] = None
    ):
        """
        Initialise le gestionnaire de suggestions.
        
        Args:
            learning_manager: Gestionnaire d'apprentissage
            embeddings_manager: Gestionnaire d'embeddings
            logger: Logger
            config: Configuration
            workflow_matcher: Matcher de workflows (optionnel)
        """
        self.learning_manager = learning_manager
        self.embeddings_manager = embeddings_manager
        self.logger = logger
        self.config = config
        
        # WorkflowMatcher pour la détection de workflows
        self.workflow_matcher = workflow_matcher or WorkflowMatcher(logger, config)
        
        # Configuration
        self.similarity_threshold = config.get("assist", {}).get(
            "similarity_threshold", 0.75
        )
        self.suggestion_timeout = config.get("assist", {}).get(
            "suggestion_timeout", 10.0  # secondes
        )
        self.workflow_confidence_threshold = config.get("workflow", {}).get(
            "min_confidence", 0.80  # 80% par défaut
        )
        
        # État actuel
        self.current_suggestion: Optional[Dict[str, Any]] = None
        self.suggestion_lock = Lock()
        self.suggestion_start_time: Optional[datetime] = None
        
        # Tracking des rejets par workflow
        self.workflow_rejections: Dict[str, int] = {}  # workflow_id -> count
        self.workflow_priority_adjustments: Dict[str, float] = {}  # workflow_id -> multiplier
        
        # Callbacks
        self.on_suggestion_created: Optional[Callable] = None
        self.on_suggestion_accepted: Optional[Callable] = None
        self.on_suggestion_rejected: Optional[Callable] = None
        self.on_suggestion_timeout: Optional[Callable] = None
        
        self.logger.log_action({
            "action": "suggestion_manager_initialized",
            "similarity_threshold": self.similarity_threshold,
            "timeout": self.suggestion_timeout,
            "workflow_confidence_threshold": self.workflow_confidence_threshold
        })
    
    def find_suggestion(self, context: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """
        Recherche une suggestion basée sur le contexte actuel.
        
        Args:
            context: Contexte actuel (embedding, fenêtre, etc.)
            
        Returns:
            Suggestion ou None
        """
        # D'abord, vérifier s'il y a un workflow en cours
        workflow_suggestion = self._check_workflow_suggestion(context)
        if workflow_suggestion:
            return workflow_suggestion
        
        # Sinon, recherche classique par embedding
        embedding = context.get("embedding")
        
        if embedding is None:
            return None
        
        # Rechercher dans FAISS
        results = self.embeddings_manager.search_similar(embedding, k=3)
        
        if not results:
            return None
        
        # Filtrer par seuil de similarité
        best_match = results[0]
        
        if best_match["similarity"] < self.similarity_threshold:
            return None
        
        # Récupérer les métadonnées
        metadata = best_match["metadata"]
        task_id = metadata.get("task_id")
        
        if not task_id:
            return None
        
        # Charger la tâche
        task = self.learning_manager.load_task(task_id)
        
        if not task:
            return None
        
        # Créer la suggestion
        suggestion = {
            "type": "action",  # Type de suggestion
            "task_id": task_id,
            "task_name": task.task_name,
            "action_type": metadata.get("action_type", "unknown"),
            "description": metadata.get("description", ""),
            "similarity": best_match["similarity"],
            "confidence": self._calculate_confidence(best_match, task),
            "metadata": metadata,
            "timestamp": datetime.now()
        }
        
        self.logger.log_action({
            "action": "suggestion_found",
            "task_id": task_id,
            "similarity": best_match["similarity"],
            "confidence": suggestion["confidence"]
        })
        
        return suggestion
    
    def _check_workflow_suggestion(self, context: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """
        Vérifie s'il y a un workflow en cours qui correspond au contexte.
        
        Args:
            context: Contexte actuel
            
        Returns:
            Suggestion de workflow ou None
        """
        # Récupérer l'event_capture depuis le contexte
        event_capture = context.get("event_capture")
        if not event_capture:
            return None
        
        # Récupérer les workflows détectés
        workflows = event_capture.get_workflows()
        if not workflows:
            return None
        
        # Récupérer la session courante
        current_session = event_capture.session_manager.current_session
        if not current_session or not current_session.actions:
            return None
        
        # Comparer avec les workflows connus
        for workflow in workflows:
            # Vérifier si on est au début d'un workflow
            match_score = self._match_workflow_start(current_session.actions, workflow)
            
            if match_score >= 0.8:  # 80% de correspondance
                # Calculer quelle est la prochaine étape
                next_step_index = len(current_session.actions)
                
                if next_step_index < len(workflow.steps):
                    next_step = workflow.steps[next_step_index]
                    
                    # Créer une suggestion de workflow
                    suggestion = {
                        "type": "workflow",  # Type workflow
                        "workflow_id": workflow.workflow_id,
                        "workflow_name": workflow.name,
                        "current_step": next_step_index,
                        "total_steps": len(workflow.steps),
                        "next_action": {
                            "action_type": next_step.action_type,
                            "description": next_step.target_description,
                            "position": next_step.position
                        },
                        "remaining_steps": len(workflow.steps) - next_step_index,
                        "confidence": workflow.confidence * match_score,
                        "repetitions": workflow.repetitions,
                        "timestamp": datetime.now()
                    }
                    
                    self.logger.log_action({
                        "action": "workflow_suggestion_found",
                        "workflow_id": workflow.workflow_id,
                        "step": next_step_index,
                        "confidence": suggestion["confidence"]
                    })
                    
                    return suggestion
        
        return None
    
    def _match_workflow_start(self, current_actions: list, workflow) -> float:
        """
        Calcule le score de correspondance entre les actions courantes et le début d'un workflow.
        
        Args:
            current_actions: Actions de la session courante
            workflow: Workflow à comparer
            
        Returns:
            Score de correspondance (0-1)
        """
        if not current_actions or not workflow.steps:
            return 0.0
        
        # Comparer les N premières actions
        n = min(len(current_actions), len(workflow.steps))
        matches = 0
        
        for i in range(n):
            action = current_actions[i]
            step = workflow.steps[i]
            
            # Comparer le type d'action
            if action.get("action_type") == step.action_type:
                matches += 1
                
                # Bonus si même fenêtre
                if action.get("window") == step.window:
                    matches += 0.5
        
        # Score normalisé
        max_score = n * 1.5  # 1 pour le type + 0.5 pour la fenêtre
        return matches / max_score if max_score > 0 else 0.0
    
    def _calculate_confidence(
        self,
        match: Dict[str, Any],
        task: Any
    ) -> float:
        """
        Calcule le score de confiance pour une suggestion.
        
        Args:
            match: Résultat de recherche FAISS
            task: Profil de tâche
            
        Returns:
            Score de confiance (0-1)
        """
        # Similarité visuelle
        vision_score = match["similarity"]
        
        # Performance historique
        historical_score = task.concordance_rate if hasattr(task, "concordance_rate") else 0.5
        
        # Formule : 70% vision + 30% historique
        confidence = 0.7 * vision_score + 0.3 * historical_score
        
        return max(0.0, min(1.0, confidence))
    
    def create_suggestion(self, context: Dict[str, Any]) -> bool:
        """
        Crée une nouvelle suggestion si applicable.
        
        Args:
            context: Contexte actuel
            
        Returns:
            True si suggestion créée
        """
        with self.suggestion_lock:
            # Vérifier qu'il n'y a pas déjà une suggestion active
            if self.current_suggestion is not None:
                return False
            
            # Rechercher une suggestion
            suggestion = self.find_suggestion(context)
            
            if suggestion is None:
                return False
            
            # Vérifier le seuil de confiance
            if suggestion["confidence"] < self.similarity_threshold:
                return False
            
            # Créer la suggestion
            self.current_suggestion = suggestion
            self.suggestion_start_time = datetime.now()
            
            # Callback
            if self.on_suggestion_created:
                self.on_suggestion_created(suggestion)
            
            self.logger.log_action({
                "action": "suggestion_created",
                "task_id": suggestion["task_id"],
                "confidence": suggestion["confidence"]
            })
            
            return True
    
    def accept_suggestion(self) -> Optional[Dict[str, Any]]:
        """
        Accepte la suggestion actuelle.
        
        Returns:
            Suggestion acceptée ou None
        """
        with self.suggestion_lock:
            if self.current_suggestion is None:
                return None
            
            suggestion = self.current_suggestion
            self.current_suggestion = None
            self.suggestion_start_time = None
            
            # Si c'est une suggestion de workflow, tracker l'acceptation
            if suggestion.get("type") == "workflow":
                workflow_id = suggestion.get("workflow_id")
                if workflow_id:
                    self._track_workflow_acceptance(workflow_id)
            
            # Mettre à jour les statistiques (pour les suggestions d'action)
            if suggestion.get("task_id"):
                self.learning_manager.confirm_action({
                    "type": "accept",
                    "task_id": suggestion["task_id"]
                })
            
            # Callback
            if self.on_suggestion_accepted:
                self.on_suggestion_accepted(suggestion)
            
            self.logger.log_action({
                "action": "suggestion_accepted",
                "suggestion_type": suggestion.get("type"),
                "workflow_id": suggestion.get("workflow_id"),
                "task_id": suggestion.get("task_id")
            })
            
            return suggestion
    
    def reject_suggestion(self) -> bool:
        """
        Rejette la suggestion actuelle.
        
        Returns:
            True si suggestion rejetée
        """
        with self.suggestion_lock:
            if self.current_suggestion is None:
                return False
            
            suggestion = self.current_suggestion
            self.current_suggestion = None
            self.suggestion_start_time = None
            
            # Si c'est une suggestion de workflow, tracker le rejet
            if suggestion.get("type") == "workflow":
                workflow_id = suggestion.get("workflow_id")
                if workflow_id:
                    self._track_workflow_rejection(workflow_id)
            
            # Mettre à jour les statistiques (pour les suggestions d'action)
            if suggestion.get("task_id"):
                self.learning_manager.confirm_action({
                    "type": "reject",
                    "task_id": suggestion["task_id"]
                })
            
            # Callback
            if self.on_suggestion_rejected:
                self.on_suggestion_rejected(suggestion)
            
            self.logger.log_action({
                "action": "suggestion_rejected",
                "suggestion_type": suggestion.get("type"),
                "workflow_id": suggestion.get("workflow_id"),
                "task_id": suggestion.get("task_id")
            })
            
            return True
    
    def check_timeout(self) -> bool:
        """
        Vérifie si la suggestion actuelle a expiré.
        
        Returns:
            True si timeout
        """
        with self.suggestion_lock:
            if self.current_suggestion is None:
                return False
            
            if self.suggestion_start_time is None:
                return False
            
            elapsed = (datetime.now() - self.suggestion_start_time).total_seconds()
            
            if elapsed >= self.suggestion_timeout:
                # Timeout
                suggestion = self.current_suggestion
                self.current_suggestion = None
                self.suggestion_start_time = None
                
                # Callback
                if self.on_suggestion_timeout:
                    self.on_suggestion_timeout(suggestion)
                
                self.logger.log_action({
                    "action": "suggestion_timeout",
                    "task_id": suggestion["task_id"],
                    "elapsed": elapsed
                })
                
                return True
            
            return False
    
    def get_current_suggestion(self) -> Optional[Dict[str, Any]]:
        """Retourne la suggestion actuelle."""
        with self.suggestion_lock:
            return self.current_suggestion
    
    def clear_suggestion(self):
        """Efface la suggestion actuelle."""
        with self.suggestion_lock:
            self.current_suggestion = None
            self.suggestion_start_time = None
    
    def get_stats(self) -> Dict[str, Any]:
        """Retourne les statistiques du gestionnaire."""
        return {
            "has_active_suggestion": self.current_suggestion is not None,
            "similarity_threshold": self.similarity_threshold,
            "timeout": self.suggestion_timeout,
            "workflow_rejections": len(self.workflow_rejections),
            "workflows_with_adjusted_priority": len(self.workflow_priority_adjustments)
        }
    
    def check_workflow_match(
        self,
        session_actions: List[Dict[str, Any]],
        workflows: List[Dict[str, Any]]
    ) -> Optional[WorkflowMatch]:
        """
        Vérifie périodiquement si les actions courantes correspondent à un workflow connu.
        
        Cette méthode doit être appelée régulièrement (ex: toutes les 2s) en mode Assist
        pour détecter les correspondances de workflows.
        
        Args:
            session_actions: Liste des actions de la session courante
            workflows: Liste des workflows connus
            
        Returns:
            Meilleure correspondance si trouvée, None sinon
        """
        if not session_actions or not workflows:
            return None
        
        # Vérifier qu'il n'y a pas déjà une suggestion active
        with self.suggestion_lock:
            if self.current_suggestion is not None:
                return None
        
        # Utiliser le WorkflowMatcher pour trouver les correspondances
        matches = self.workflow_matcher.match_current_session(
            session_actions,
            workflows
        )
        
        if not matches:
            return None
        
        # Appliquer les ajustements de priorité basés sur les rejets
        adjusted_matches = []
        for match in matches:
            adjusted_confidence = self._apply_priority_adjustment(match)
            
            # Créer un nouveau match avec la confiance ajustée
            adjusted_match = WorkflowMatch(
                workflow_id=match.workflow_id,
                workflow_name=match.workflow_name,
                confidence=adjusted_confidence,
                matched_steps=match.matched_steps,
                total_steps=match.total_steps,
                remaining_steps=match.remaining_steps,
                current_step_index=match.current_step_index
            )
            adjusted_matches.append(adjusted_match)
        
        # Trier à nouveau par confiance ajustée
        adjusted_matches.sort(key=lambda m: m.confidence, reverse=True)
        
        # Trouver le meilleur match
        best_match = self.workflow_matcher.find_best_match(adjusted_matches)
        
        if best_match:
            self.logger.log_action({
                "action": "workflow_match_found",
                "workflow_id": best_match.workflow_id,
                "workflow_name": best_match.workflow_name,
                "confidence": best_match.confidence,
                "matched_steps": best_match.matched_steps,
                "remaining_steps": len(best_match.remaining_steps)
            })
        
        return best_match
    
    def create_workflow_suggestion(
        self,
        workflow_match: WorkflowMatch
    ) -> Optional[Dict[str, Any]]:
        """
        Crée une suggestion de workflow avec les détails des étapes restantes.
        
        Args:
            workflow_match: Correspondance de workflow trouvée
            
        Returns:
            Suggestion créée ou None si impossible
        """
        with self.suggestion_lock:
            # Vérifier qu'il n'y a pas déjà une suggestion active
            if self.current_suggestion is not None:
                return None
            
            # Vérifier le seuil de confiance
            if workflow_match.confidence < self.workflow_confidence_threshold:
                self.logger.log_action({
                    "action": "workflow_suggestion_rejected_low_confidence",
                    "workflow_id": workflow_match.workflow_id,
                    "confidence": workflow_match.confidence,
                    "threshold": self.workflow_confidence_threshold
                })
                return None
            
            # Créer la suggestion avec les détails des étapes
            suggestion = {
                "type": "workflow",
                "workflow_id": workflow_match.workflow_id,
                "workflow_name": workflow_match.workflow_name,
                "confidence": workflow_match.confidence,
                "current_step": workflow_match.current_step_index,
                "total_steps": workflow_match.total_steps,
                "matched_steps": workflow_match.matched_steps,
                "remaining_steps": workflow_match.remaining_steps,
                "next_steps_preview": workflow_match.remaining_steps[:3],  # 3 prochaines étapes
                "created_at": datetime.now(),
                "timeout": self.suggestion_timeout
            }
            
            # Enregistrer la suggestion
            self.current_suggestion = suggestion
            self.suggestion_start_time = datetime.now()
            
            # Callback
            if self.on_suggestion_created:
                self.on_suggestion_created(suggestion)
            
            self.logger.log_action({
                "action": "workflow_suggestion_created",
                "workflow_id": workflow_match.workflow_id,
                "workflow_name": workflow_match.workflow_name,
                "confidence": workflow_match.confidence,
                "remaining_steps": len(workflow_match.remaining_steps)
            })
            
            return suggestion
    
    def _apply_priority_adjustment(self, match: WorkflowMatch) -> float:
        """
        Applique l'ajustement de priorité basé sur les rejets précédents.
        
        Args:
            match: Correspondance de workflow
            
        Returns:
            Confiance ajustée
        """
        workflow_id = match.workflow_id
        
        # Récupérer le multiplicateur d'ajustement
        adjustment = self.workflow_priority_adjustments.get(workflow_id, 1.0)
        
        # Appliquer l'ajustement
        adjusted_confidence = match.confidence * adjustment
        
        # S'assurer que la confiance reste dans [0, 1]
        adjusted_confidence = max(0.0, min(1.0, adjusted_confidence))
        
        if adjustment != 1.0:
            self.logger.log_action({
                "action": "priority_adjustment_applied",
                "workflow_id": workflow_id,
                "original_confidence": match.confidence,
                "adjusted_confidence": adjusted_confidence,
                "adjustment_multiplier": adjustment
            })
        
        return adjusted_confidence
    
    def _track_workflow_rejection(self, workflow_id: str):
        """
        Enregistre un rejet de workflow et ajuste la priorité si nécessaire.
        
        Après 3 rejets, la priorité du workflow est réduite (confiance * 0.9).
        
        Args:
            workflow_id: ID du workflow rejeté
        """
        # Incrémenter le compteur de rejets
        current_rejections = self.workflow_rejections.get(workflow_id, 0)
        current_rejections += 1
        self.workflow_rejections[workflow_id] = current_rejections
        
        self.logger.log_action({
            "action": "workflow_rejection_tracked",
            "workflow_id": workflow_id,
            "total_rejections": current_rejections
        })
        
        # Après 3 rejets, ajuster la priorité
        if current_rejections >= 3:
            # Réduire la confiance de 10% à chaque tranche de 3 rejets
            adjustment_factor = 0.9 ** (current_rejections // 3)
            self.workflow_priority_adjustments[workflow_id] = adjustment_factor
            
            self.logger.log_action({
                "action": "workflow_priority_adjusted",
                "workflow_id": workflow_id,
                "rejections": current_rejections,
                "new_adjustment_factor": adjustment_factor
            })
    
    def _track_workflow_acceptance(self, workflow_id: str):
        """
        Enregistre une acceptation de workflow et améliore la priorité.
        
        Args:
            workflow_id: ID du workflow accepté
        """
        # Réduire le compteur de rejets (récompenser les acceptations)
        current_rejections = self.workflow_rejections.get(workflow_id, 0)
        if current_rejections > 0:
            current_rejections = max(0, current_rejections - 2)  # Réduire de 2
            self.workflow_rejections[workflow_id] = current_rejections
            
            # Recalculer l'ajustement de priorité
            if current_rejections >= 3:
                adjustment_factor = 0.9 ** (current_rejections // 3)
                self.workflow_priority_adjustments[workflow_id] = adjustment_factor
            else:
                # Retirer l'ajustement si moins de 3 rejets
                if workflow_id in self.workflow_priority_adjustments:
                    del self.workflow_priority_adjustments[workflow_id]
            
            self.logger.log_action({
                "action": "workflow_acceptance_tracked",
                "workflow_id": workflow_id,
                "remaining_rejections": current_rejections
            })

    def on_workflow_detected(self, workflow: Dict[str, Any]):
        """
        Callback appelé quand un workflow est détecté.
        Peut créer une suggestion immédiate si le workflow est pertinent.
        
        Args:
            workflow: Workflow détecté
        """
        self.logger.log_action({
            "action": "workflow_detected_in_suggestion_manager",
            "workflow_id": workflow.get("workflow_id"),
            "workflow_name": workflow.get("name"),
            "confidence": workflow.get("confidence")
        })
        
        # Pour l'instant, on log seulement
        # Dans le futur, on pourrait créer une suggestion proactive
        # basée sur le workflow détecté
