"""
Détecteur de workflows pour identifier les séquences répétitives.
Analyse les sessions pour détecter des patterns de workflows.
"""

from datetime import datetime
from typing import List, Dict, Any, Optional, Callable
from dataclasses import dataclass, field, asdict
from pathlib import Path
import numpy as np
import json


@dataclass
class WorkflowStep:
    """Représente une étape dans un workflow."""
    step_id: int
    action_type: str
    target_description: str
    position: tuple
    window: str
    embedding: Optional[np.ndarray] = None
    screenshot: Optional[np.ndarray] = None


@dataclass
class Workflow:
    """Représente un workflow détecté."""
    workflow_id: str
    name: str
    steps: List[WorkflowStep]
    repetitions: int
    confidence: float
    last_seen: datetime
    created_at: datetime = field(default_factory=datetime.now)


class WorkflowDetector:
    """
    Détecteur de workflows pour identifier les séquences répétitives.
    """
    
    def __init__(self, logger, config: Dict[str, Any]):
        """
        Initialise le détecteur de workflows.
        
        Args:
            logger: Logger pour journalisation
            config: Configuration globale
        """
        self.logger = logger
        self.config = config
        
        # Configuration
        self.min_repetitions = config.get("workflow", {}).get(
            "min_repetitions", 3  # 3 répétitions minimum
        )
        self.similarity_threshold = config.get("workflow", {}).get(
            "similarity_threshold", 0.75  # 75% de similarité
        )
        self.min_workflow_length = config.get("workflow", {}).get(
            "min_workflow_length", 3  # 3 actions minimum pour un workflow
        )
        
        # Workflows détectés
        self.workflows: List[Workflow] = []
        
        # Buffer de sessions récentes pour analyse
        self.recent_sessions: List[Any] = []
        self.max_recent_sessions = 10  # Garder les 10 dernières sessions
        
        # Callback pour workflow détecté
        self.on_workflow_detected: Optional[Callable] = None
        
        # Répertoire de persistence
        self.workflows_dir = Path(config.get("data_dir", "data")) / "user_profiles" / "workflows"
        self.workflows_dir.mkdir(parents=True, exist_ok=True)
        
        # Charger les workflows existants
        self._load_workflows()
        
        self.logger.log_action({
            "action": "workflow_detector_initialized",
            "min_repetitions": self.min_repetitions,
            "similarity_threshold": self.similarity_threshold,
            "workflows_loaded": len(self.workflows)
        })
    
    def analyze_session(self, session: Any):
        """
        Analyse une session individuelle et l'ajoute au buffer.
        Détecte des workflows si suffisamment de sessions similaires.
        
        Args:
            session: Session à analyser
        """
        # Ajouter au buffer
        self.recent_sessions.append(session)
        
        # Limiter la taille du buffer
        if len(self.recent_sessions) > self.max_recent_sessions:
            self.recent_sessions.pop(0)
        
        # Analyser si on a assez de sessions
        if len(self.recent_sessions) >= self.min_repetitions:
            self.analyze_sessions(self.recent_sessions)
    
    def analyze_sessions(self, sessions: List[Any]):
        """
        Analyse les sessions pour détecter des workflows.
        
        Args:
            sessions: Liste de sessions à analyser
        """
        if len(sessions) < self.min_repetitions:
            return
        
        # Grouper les sessions par fenêtre
        sessions_by_window = {}
        for session in sessions:
            window = session.window or "Unknown"
            if window not in sessions_by_window:
                sessions_by_window[window] = []
            sessions_by_window[window].append(session)
        
        # Analyser chaque groupe de fenêtre séparément
        for window, window_sessions in sessions_by_window.items():
            if len(window_sessions) < self.min_repetitions:
                continue
            
            # Comparer les sessions de cette fenêtre
            for i in range(len(window_sessions) - self.min_repetitions + 1):
                # Prendre N sessions consécutives
                session_group = window_sessions[i:i + self.min_repetitions]
                
                # Calculer la similarité entre elles
                similarity = self._calculate_session_similarity(session_group)
                
                if similarity >= self.similarity_threshold:
                    # Workflow détecté !
                    workflow = self._create_workflow_from_sessions(session_group)
                    
                    # Ignorer les workflows trop courts (probablement du bruit)
                    if len(workflow.steps) < self.min_workflow_length:
                        continue
                    
                    # Vérifier si ce workflow existe déjà
                    existing = self._find_existing_workflow(workflow)
                    
                    if existing:
                        # Mettre à jour
                        existing.repetitions += 1
                        existing.last_seen = datetime.now()
                        existing.confidence = min(1.0, existing.confidence + 0.05)
                        
                        # Sauvegarder les modifications
                        self._save_workflow(existing)
                    else:
                        # Nouveau workflow
                        self.workflows.append(workflow)
                        
                        # Sauvegarder sur disque
                        self._save_workflow(workflow)
                        
                        # Callback avec dictionnaire
                        if self.on_workflow_detected:
                            workflow_dict = {
                                "workflow_id": workflow.workflow_id,
                                "name": workflow.name,
                                "pattern": [step.action_type for step in workflow.steps],
                                "steps": workflow.steps,
                                "confidence": workflow.confidence,
                                "repetitions": workflow.repetitions,
                                "last_seen": workflow.last_seen,
                                "window": workflow.steps[0].window if workflow.steps else ""
                            }
                            self.on_workflow_detected(workflow_dict)
                        
                        self.logger.log_action({
                            "action": "workflow_detected",
                            "workflow_id": workflow.workflow_id,
                            "workflow_name": workflow.name,
                            "steps": len(workflow.steps),
                            "confidence": workflow.confidence
                        })
    
    def _calculate_session_similarity(self, sessions: List[Any]) -> float:
        """
        Calcule la similarité entre plusieurs sessions.
        
        Args:
            sessions: Sessions à comparer
            
        Returns:
            Score de similarité (0-1)
        """
        if len(sessions) < 2:
            return 0.0
        
        # Comparer chaque paire de sessions
        similarities = []
        
        for i in range(len(sessions) - 1):
            for j in range(i + 1, len(sessions)):
                sim = self._compare_two_sessions(sessions[i], sessions[j])
                similarities.append(sim)
        
        # Moyenne des similarités
        return np.mean(similarities) if similarities else 0.0
    
    def _compare_two_sessions(self, session1: Any, session2: Any) -> float:
        """
        Compare deux sessions.
        
        Args:
            session1: Première session
            session2: Deuxième session
            
        Returns:
            Score de similarité (0-1)
        """
        actions1 = session1.actions
        actions2 = session2.actions
        
        # Vérifier la longueur
        if abs(len(actions1) - len(actions2)) > 2:
            return 0.0
        
        # Comparer les actions
        min_len = min(len(actions1), len(actions2))
        matches = 0
        
        for i in range(min_len):
            a1 = actions1[i]
            a2 = actions2[i]
            
            # Comparer le type d'action (peut être "type" ou "action_type")
            type1 = a1.get("action_type") or a1.get("type")
            type2 = a2.get("action_type") or a2.get("type")
            
            if type1 == type2:
                matches += 1
                
                # Bonus si même fenêtre
                if a1.get("window") == a2.get("window"):
                    matches += 0.5
        
        # Score normalisé
        max_score = min_len * 1.5
        return matches / max_score if max_score > 0 else 0.0
    
    def _create_workflow_from_sessions(self, sessions: List[Any]) -> Workflow:
        """
        Crée un workflow à partir de sessions similaires.
        
        Args:
            sessions: Sessions à fusionner
            
        Returns:
            Workflow créé
        """
        # Prendre la première session comme référence
        reference = sessions[0]
        
        # Créer les étapes
        steps = []
        for i, action in enumerate(reference.actions):
            # Gérer les deux formats d'action
            action_type = action.get("action_type") or action.get("type", "unknown")
            position = action.get("position") or (action.get("x", 0), action.get("y", 0))
            
            step = WorkflowStep(
                step_id=i,
                action_type=action_type,
                target_description=action.get("description", ""),
                position=position,
                window=action.get("window", ""),
                embedding=action.get("embedding")
            )
            steps.append(step)
        
        # Générer un nom
        workflow_name = self._generate_workflow_name(steps)
        
        # Créer le workflow
        workflow_id = f"workflow_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
        
        return Workflow(
            workflow_id=workflow_id,
            name=workflow_name,
            steps=steps,
            repetitions=len(sessions),
            confidence=0.8,  # Confiance initiale
            last_seen=datetime.now()
        )
    
    def _generate_workflow_name(self, steps: List[WorkflowStep]) -> str:
        """
        Génère un nom pour le workflow.
        
        Args:
            steps: Étapes du workflow
            
        Returns:
            Nom généré
        """
        if not steps:
            return "Workflow vide"
        
        # Utiliser les premières actions
        first_actions = [s.action_type for s in steps[:3]]
        
        # Créer un nom descriptif
        if len(steps) == 1:
            return f"{steps[0].action_type.capitalize()}"
        elif len(steps) <= 3:
            return " → ".join(a.capitalize() for a in first_actions)
        else:
            return f"{' → '.join(a.capitalize() for a in first_actions)} (+ {len(steps) - 3} étapes)"
    
    def _find_existing_workflow(self, workflow: Workflow) -> Optional[Workflow]:
        """
        Cherche un workflow existant similaire.
        
        Args:
            workflow: Workflow à chercher
            
        Returns:
            Workflow existant ou None
        """
        for existing in self.workflows:
            # Comparer le nombre d'étapes
            if len(existing.steps) != len(workflow.steps):
                continue
            
            # Comparer les types d'actions
            matches = 0
            for i in range(len(existing.steps)):
                if existing.steps[i].action_type == workflow.steps[i].action_type:
                    matches += 1
            
            similarity = matches / len(existing.steps)
            
            if similarity >= 0.8:  # 80% de correspondance
                return existing
        
        return None
    
    def get_workflows(self) -> List[Workflow]:
        """Retourne tous les workflows détectés."""
        return self.workflows
    
    def get_stats(self) -> Dict[str, Any]:
        """Retourne les statistiques des workflows."""
        return {
            "total_workflows": len(self.workflows),
            "avg_steps": np.mean([len(w.steps) for w in self.workflows]) if self.workflows else 0,
            "avg_repetitions": np.mean([w.repetitions for w in self.workflows]) if self.workflows else 0,
            "avg_confidence": np.mean([w.confidence for w in self.workflows]) if self.workflows else 0
        }

    def _save_workflow(self, workflow: Workflow):
        """
        Sauvegarde un workflow sur disque.
        
        Args:
            workflow: Workflow à sauvegarder
        """
        try:
            workflow_file = self.workflows_dir / f"{workflow.workflow_id}.json"
            
            # Convertir en dictionnaire
            workflow_dict = {
                "workflow_id": workflow.workflow_id,
                "name": workflow.name,
                "repetitions": workflow.repetitions,
                "confidence": workflow.confidence,
                "last_seen": workflow.last_seen.isoformat(),
                "created_at": workflow.created_at.isoformat(),
                "steps": [
                    {
                        "step_id": step.step_id,
                        "action_type": step.action_type,
                        "target_description": step.target_description,
                        "position": step.position,
                        "window": step.window
                        # Note: on ne sauvegarde pas embedding et screenshot (trop gros)
                    }
                    for step in workflow.steps
                ]
            }
            
            # Sauvegarder
            with open(workflow_file, 'w') as f:
                json.dump(workflow_dict, f, indent=2)
            
            self.logger.log_action({
                "action": "workflow_saved",
                "workflow_id": workflow.workflow_id,
                "file": str(workflow_file)
            })
            
        except Exception as e:
            self.logger.log_action({
                "action": "workflow_save_failed",
                "workflow_id": workflow.workflow_id,
                "error": str(e)
            })
    
    def _load_workflows(self):
        """Charge les workflows depuis le disque."""
        try:
            if not self.workflows_dir.exists():
                return
            
            workflow_files = list(self.workflows_dir.glob("*.json"))
            
            for workflow_file in workflow_files:
                try:
                    with open(workflow_file, 'r') as f:
                        data = json.load(f)
                    
                    # Reconstruire le workflow
                    steps = [
                        WorkflowStep(
                            step_id=step_data["step_id"],
                            action_type=step_data["action_type"],
                            target_description=step_data["target_description"],
                            position=tuple(step_data["position"]),
                            window=step_data["window"]
                        )
                        for step_data in data["steps"]
                    ]
                    
                    workflow = Workflow(
                        workflow_id=data["workflow_id"],
                        name=data["name"],
                        steps=steps,
                        repetitions=data["repetitions"],
                        confidence=data["confidence"],
                        last_seen=datetime.fromisoformat(data["last_seen"]),
                        created_at=datetime.fromisoformat(data["created_at"])
                    )
                    
                    self.workflows.append(workflow)
                    
                except Exception as e:
                    self.logger.log_action({
                        "action": "workflow_load_failed",
                        "file": str(workflow_file),
                        "error": str(e)
                    })
            
            self.logger.log_action({
                "action": "workflows_loaded",
                "count": len(self.workflows)
            })
            
        except Exception as e:
            self.logger.log_action({
                "action": "workflows_load_error",
                "error": str(e)
            })
    
    def update_workflow_confidence(self, workflow_id: str, success: bool):
        """
        Met à jour la confiance d'un workflow après exécution.
        
        Args:
            workflow_id: ID du workflow
            success: True si l'exécution a réussi
        """
        for workflow in self.workflows:
            if workflow.workflow_id == workflow_id:
                if success:
                    workflow.confidence = min(1.0, workflow.confidence + 0.05)
                else:
                    workflow.confidence = max(0.0, workflow.confidence - 0.1)
                
                workflow.last_seen = datetime.now()
                
                # Sauvegarder
                self._save_workflow(workflow)
                
                self.logger.log_action({
                    "action": "workflow_confidence_updated",
                    "workflow_id": workflow_id,
                    "new_confidence": workflow.confidence,
                    "success": success
                })
                
                break
