"""
Moteur de rejeu d'actions pour rollback asynchrone.
Permet de rejouer des séquences d'actions et d'annuler les dernières actions.
"""

import asyncio
import time
from typing import List, Dict, Any, Optional, Callable
from enum import Enum

from .utils.input_utils import InputUtils, ActionType
from .logger import Logger


class ReplayStatus(Enum):
    """Statuts de rejeu."""
    IDLE = "idle"
    REPLAYING = "replaying"
    ROLLING_BACK = "rolling_back"
    PAUSED = "paused"
    COMPLETED = "completed"
    FAILED = "failed"


class ReplayEngine:
    """
    Moteur de rejeu d'actions pour exécution asynchrone et rollback.
    """
    
    def __init__(
        self,
        input_utils: InputUtils,
        logger: Logger,
        config: Dict[str, Any]
    ):
        """
        Initialise le moteur de rejeu.
        
        Args:
            input_utils: Utilitaires d'entrée pour exécuter actions
            logger: Logger pour journalisation
            config: Configuration globale
        """
        self.input_utils = input_utils
        self.logger = logger
        self.config = config
        
        # État du moteur
        self.status = ReplayStatus.IDLE
        self.current_sequence: List[Dict[str, Any]] = []
        self.current_index = 0
        
        # Callbacks pour notifications
        self.on_action_executed: Optional[Callable] = None
        self.on_sequence_completed: Optional[Callable] = None
        self.on_rollback_completed: Optional[Callable] = None
        self.on_error: Optional[Callable] = None
        
        # Configuration
        self.delay_between_actions = config.get("replay", {}).get(
            "delay_between_actions", 0.5
        )
        self.max_rollback_attempts = config.get("replay", {}).get(
            "max_rollback_attempts", 3
        )
        
        self.logger.log_action({
            "action": "replay_engine_initialized",
            "delay_between_actions": self.delay_between_actions,
            "max_rollback_attempts": self.max_rollback_attempts
        })
    
    async def replay_sequence(
        self,
        action_sequence: List[Dict[str, Any]],
        start_index: int = 0
    ) -> bool:
        """
        Rejoue une séquence d'actions de manière asynchrone.
        
        Args:
            action_sequence: Liste d'actions à rejouer
            start_index: Index de départ dans la séquence
            
        Returns:
            True si succès complet, False sinon
        """
        if self.status != ReplayStatus.IDLE:
            self.logger.log_action({
                "action": "replay_rejected",
                "reason": "engine_busy",
                "current_status": self.status.value
            })
            return False
        
        self.status = ReplayStatus.REPLAYING
        self.current_sequence = action_sequence
        self.current_index = start_index
        
        self.logger.log_action({
            "action": "replay_sequence_started",
            "num_actions": len(action_sequence),
            "start_index": start_index
        })
        
        try:
            for i in range(start_index, len(action_sequence)):
                if self.status == ReplayStatus.PAUSED:
                    # Attendre la reprise
                    while self.status == ReplayStatus.PAUSED:
                        await asyncio.sleep(0.1)
                
                if self.status != ReplayStatus.REPLAYING:
                    # Arrêt demandé
                    break
                
                action = action_sequence[i]
                self.current_index = i
                
                # Exécuter l'action
                success = await self._execute_action(action)
                
                if not success:
                    self.logger.log_action({
                        "action": "replay_action_failed",
                        "index": i,
                        "action_type": action.get("type")
                    })
                    
                    if self.on_error:
                        self.on_error(i, action)
                    
                    self.status = ReplayStatus.FAILED
                    return False
                
                # Notifier l'exécution
                if self.on_action_executed:
                    self.on_action_executed(i, action)
                
                # Attendre entre les actions
                if i < len(action_sequence) - 1:
                    await asyncio.sleep(self.delay_between_actions)
            
            # Séquence terminée avec succès
            self.status = ReplayStatus.COMPLETED
            
            self.logger.log_action({
                "action": "replay_sequence_completed",
                "num_actions_executed": len(action_sequence) - start_index
            })
            
            if self.on_sequence_completed:
                self.on_sequence_completed()
            
            return True
        
        except Exception as e:
            self.logger.log_action({
                "action": "replay_sequence_error",
                "error": str(e),
                "index": self.current_index
            })
            
            self.status = ReplayStatus.FAILED
            
            if self.on_error:
                self.on_error(self.current_index, None)
            
            return False
        
        finally:
            if self.status in [ReplayStatus.COMPLETED, ReplayStatus.FAILED]:
                self.status = ReplayStatus.IDLE
    
    async def rollback_last_n(self, n: int) -> bool:
        """
        Annule les n dernières actions en exécutant leurs inverses.
        
        Args:
            n: Nombre d'actions à annuler
            
        Returns:
            True si rollback réussi, False sinon
        """
        if self.status != ReplayStatus.IDLE:
            self.logger.log_action({
                "action": "rollback_rejected",
                "reason": "engine_busy",
                "current_status": self.status.value
            })
            return False
        
        # Récupérer les dernières actions de l'historique
        action_history = self.input_utils.get_action_history(limit=n)
        
        if len(action_history) < n:
            self.logger.log_action({
                "action": "rollback_partial",
                "requested": n,
                "available": len(action_history)
            })
            n = len(action_history)
        
        if n == 0:
            return True
        
        self.status = ReplayStatus.ROLLING_BACK
        
        self.logger.log_action({
            "action": "rollback_started",
            "num_actions": n
        })
        
        try:
            # Inverser les actions (de la plus récente à la plus ancienne)
            actions_to_rollback = list(reversed(action_history[-n:]))
            
            success_count = 0
            for i, action in enumerate(actions_to_rollback):
                # Générer l'action inverse
                inverse_action = self.input_utils.get_inverse_action(action)
                
                if inverse_action is None:
                    self.logger.log_action({
                        "action": "rollback_action_not_invertible",
                        "index": i,
                        "action_type": action.get("type")
                    })
                    continue
                
                # Exécuter l'action inverse avec retry
                success = await self._execute_action_with_retry(
                    inverse_action,
                    max_attempts=self.max_rollback_attempts
                )
                
                if success:
                    success_count += 1
                else:
                    self.logger.log_action({
                        "action": "rollback_action_failed",
                        "index": i,
                        "action_type": action.get("type"),
                        "inverse_action": inverse_action
                    })
                
                # Attendre entre les actions
                if i < len(actions_to_rollback) - 1:
                    await asyncio.sleep(self.delay_between_actions)
            
            # Vérifier le succès
            all_success = success_count == len(actions_to_rollback)
            
            self.logger.log_action({
                "action": "rollback_completed",
                "total_actions": len(actions_to_rollback),
                "successful": success_count,
                "failed": len(actions_to_rollback) - success_count,
                "all_success": all_success
            })
            
            if self.on_rollback_completed:
                self.on_rollback_completed(all_success, success_count, len(actions_to_rollback))
            
            return all_success
        
        except Exception as e:
            self.logger.log_action({
                "action": "rollback_error",
                "error": str(e)
            })
            
            if self.on_error:
                self.on_error(-1, None)
            
            return False
        
        finally:
            self.status = ReplayStatus.IDLE
    
    async def execute_inverse_actions(
        self,
        actions: List[Dict[str, Any]]
    ) -> bool:
        """
        Exécute les actions inverses d'une liste d'actions.
        
        Args:
            actions: Liste d'actions à inverser et exécuter
            
        Returns:
            True si toutes les actions inverses ont été exécutées
        """
        if self.status != ReplayStatus.IDLE:
            return False
        
        self.status = ReplayStatus.ROLLING_BACK
        
        try:
            # Inverser l'ordre et générer les actions inverses
            inverse_actions = []
            for action in reversed(actions):
                inverse = self.input_utils.get_inverse_action(action)
                if inverse:
                    inverse_actions.append(inverse)
            
            # Exécuter les actions inverses
            success = await self.replay_sequence(inverse_actions)
            
            return success
        
        finally:
            self.status = ReplayStatus.IDLE
    
    async def _execute_action(self, action: Dict[str, Any]) -> bool:
        """
        Exécute une action unique.
        
        Args:
            action: Action à exécuter
            
        Returns:
            True si succès, False sinon
        """
        action_type = action.get("type")
        
        try:
            if action_type == ActionType.CLICK.value:
                return self.input_utils.click(
                    action["x"],
                    action["y"],
                    button=action.get("button", "left"),
                    clicks=action.get("clicks", 1)
                )
            
            elif action_type == ActionType.TYPE.value:
                return self.input_utils.type_text(
                    action["text"],
                    interval=action.get("interval", 0.0)
                )
            
            elif action_type == ActionType.SCROLL.value:
                return self.input_utils.scroll(
                    action["direction"],
                    amount=action.get("amount", 3),
                    x=action.get("x"),
                    y=action.get("y")
                )
            
            elif action_type == ActionType.WAIT.value:
                return self.input_utils.wait(action["duration"])
            
            elif action_type == ActionType.MOVE.value:
                return self.input_utils.move(
                    action["x"],
                    action["y"],
                    duration=action.get("duration", 0.2)
                )
            
            elif action_type == ActionType.DRAG.value:
                return self.input_utils.drag(
                    action["start_x"],
                    action["start_y"],
                    action["end_x"],
                    action["end_y"],
                    duration=action.get("duration", 0.5),
                    button=action.get("button", "left")
                )
            
            elif action_type == "press_key":
                # Action spéciale pour rollback de saisie
                import pyautogui
                key = action.get("key")
                presses = action.get("presses", 1)
                for _ in range(presses):
                    pyautogui.press(key)
                    await asyncio.sleep(0.05)
                return True
            
            else:
                self.logger.log_action({
                    "action": "unknown_action_type",
                    "type": action_type
                })
                return False
        
        except Exception as e:
            self.logger.log_action({
                "action": "execute_action_error",
                "action_type": action_type,
                "error": str(e)
            })
            return False
    
    async def _execute_action_with_retry(
        self,
        action: Dict[str, Any],
        max_attempts: int = 3
    ) -> bool:
        """
        Exécute une action avec retry en cas d'échec.
        
        Args:
            action: Action à exécuter
            max_attempts: Nombre maximum de tentatives
            
        Returns:
            True si succès, False sinon
        """
        for attempt in range(max_attempts):
            success = await self._execute_action(action)
            
            if success:
                return True
            
            if attempt < max_attempts - 1:
                # Attendre avant de réessayer
                await asyncio.sleep(0.5)
                self.logger.log_action({
                    "action": "action_retry",
                    "attempt": attempt + 1,
                    "max_attempts": max_attempts
                })
        
        return False
    
    def pause(self):
        """Met en pause le rejeu en cours."""
        if self.status == ReplayStatus.REPLAYING:
            self.status = ReplayStatus.PAUSED
            self.logger.log_action({"action": "replay_paused"})
    
    def resume(self):
        """Reprend le rejeu en pause."""
        if self.status == ReplayStatus.PAUSED:
            self.status = ReplayStatus.REPLAYING
            self.logger.log_action({"action": "replay_resumed"})
    
    def stop(self):
        """Arrête le rejeu en cours."""
        if self.status in [ReplayStatus.REPLAYING, ReplayStatus.PAUSED]:
            self.status = ReplayStatus.IDLE
            self.logger.log_action({"action": "replay_stopped"})
    
    def get_status(self) -> Dict[str, Any]:
        """
        Retourne l'état actuel du moteur.
        
        Returns:
            Dictionnaire avec l'état
        """
        return {
            "status": self.status.value,
            "current_index": self.current_index,
            "total_actions": len(self.current_sequence),
            "progress": (
                self.current_index / len(self.current_sequence)
                if len(self.current_sequence) > 0 else 0
            )
        }
