"""
Utilitaires d'entrée pour exécuter des actions UI (souris, clavier, etc.).
Support du clavier AZERTY et gestion du rollback d'actions.
"""

import time
import pyautogui
from typing import Dict, Any, Optional, Tuple
from enum import Enum

from ..logger import Logger


class ActionType(Enum):
    """Types d'actions UI supportées."""
    CLICK = "click"
    TYPE = "type"
    SCROLL = "scroll"
    WAIT = "wait"
    MOVE = "move"
    DRAG = "drag"


class InputUtils:
    """
    Gestionnaire d'entrées utilisateur pour exécuter des actions UI.
    Support du clavier AZERTY et rollback d'actions.
    """
    
    def __init__(self, logger: Logger, config: Dict[str, Any]):
        """
        Initialise les utilitaires d'entrée.
        
        Args:
            logger: Logger pour journalisation
            config: Configuration globale
        """
        self.logger = logger
        self.config = config
        
        # Configuration PyAutoGUI
        pyautogui.FAILSAFE = True  # Déplacer souris dans coin = arrêt
        pyautogui.PAUSE = config.get("input", {}).get("pause_between_actions", 0.1)
        
        # Historique des actions pour rollback
        self.action_history = []
        
        # Mapping AZERTY pour caractères spéciaux
        self.azerty_mapping = {
            '0': 'à',
            '1': '&',
            '2': 'é',
            '3': '"',
            '4': "'",
            '5': '(',
            '6': '-',
            '7': 'è',
            '8': '_',
            '9': 'ç',
            '.': ':',
            '/': '!',
            ',': ';',
            ';': ',',
            ':': '.',
            '!': '/',
            '?': 'M',  # Shift + ,
        }
        
        self.logger.log_action({
            "action": "input_utils_initialized",
            "failsafe": True,
            "pause": pyautogui.PAUSE
        })
    
    def click(
        self,
        x: int,
        y: int,
        button: str = "left",
        clicks: int = 1,
        interval: float = 0.0
    ) -> bool:
        """
        Effectue un clic souris à la position spécifiée.
        
        Args:
            x: Coordonnée X
            y: Coordonnée Y
            button: Bouton souris ("left", "right", "middle")
            clicks: Nombre de clics
            interval: Intervalle entre clics multiples
            
        Returns:
            True si succès, False sinon
        """
        try:
            # Enregistrer position actuelle pour rollback
            current_pos = pyautogui.position()
            
            # Effectuer le clic
            pyautogui.click(x, y, clicks=clicks, interval=interval, button=button)
            
            # Enregistrer dans l'historique
            action_record = {
                "type": ActionType.CLICK.value,
                "x": x,
                "y": y,
                "button": button,
                "clicks": clicks,
                "previous_position": current_pos,
                "timestamp": time.time()
            }
            self.action_history.append(action_record)
            
            self.logger.log_action({
                "action": "click_executed",
                **action_record
            })
            
            return True
        
        except Exception as e:
            self.logger.log_action({
                "action": "click_failed",
                "x": x,
                "y": y,
                "error": str(e)
            })
            return False
    
    def type_text(
        self,
        text: str,
        interval: float = 0.0,
        use_azerty: bool = True
    ) -> bool:
        """
        Saisit du texte au clavier.
        
        Args:
            text: Texte à saisir
            interval: Intervalle entre chaque caractère
            use_azerty: Utiliser le mapping AZERTY
            
        Returns:
            True si succès, False sinon
        """
        try:
            # Convertir pour AZERTY si nécessaire
            if use_azerty:
                converted_text = self._convert_to_azerty(text)
            else:
                converted_text = text
            
            # Saisir le texte
            pyautogui.write(converted_text, interval=interval)
            
            # Enregistrer dans l'historique
            action_record = {
                "type": ActionType.TYPE.value,
                "text": text,
                "converted_text": converted_text,
                "length": len(text),
                "timestamp": time.time()
            }
            self.action_history.append(action_record)
            
            self.logger.log_action({
                "action": "text_typed",
                **action_record
            })
            
            return True
        
        except Exception as e:
            self.logger.log_action({
                "action": "type_text_failed",
                "text": text[:50],  # Limiter pour logs
                "error": str(e)
            })
            return False
    
    def scroll(
        self,
        direction: str,
        amount: int = 3,
        x: Optional[int] = None,
        y: Optional[int] = None
    ) -> bool:
        """
        Effectue un défilement.
        
        Args:
            direction: Direction ("up", "down", "left", "right")
            amount: Quantité de défilement (nombre de "clics" de molette)
            x: Position X optionnelle
            y: Position Y optionnelle
            
        Returns:
            True si succès, False sinon
        """
        try:
            # Calculer le montant de défilement
            if direction in ["up", "right"]:
                scroll_amount = amount
            elif direction in ["down", "left"]:
                scroll_amount = -amount
            else:
                raise ValueError(f"Direction invalide: {direction}")
            
            # Déplacer la souris si position spécifiée
            if x is not None and y is not None:
                pyautogui.moveTo(x, y)
            
            # Effectuer le défilement
            if direction in ["up", "down"]:
                pyautogui.scroll(scroll_amount)
            else:
                pyautogui.hscroll(scroll_amount)
            
            # Enregistrer dans l'historique
            action_record = {
                "type": ActionType.SCROLL.value,
                "direction": direction,
                "amount": amount,
                "x": x,
                "y": y,
                "timestamp": time.time()
            }
            self.action_history.append(action_record)
            
            self.logger.log_action({
                "action": "scroll_executed",
                **action_record
            })
            
            return True
        
        except Exception as e:
            self.logger.log_action({
                "action": "scroll_failed",
                "direction": direction,
                "amount": amount,
                "error": str(e)
            })
            return False
    
    def wait(self, duration: float) -> bool:
        """
        Attend pendant une durée spécifiée.
        
        Args:
            duration: Durée en secondes
            
        Returns:
            True
        """
        try:
            time.sleep(duration)
            
            action_record = {
                "type": ActionType.WAIT.value,
                "duration": duration,
                "timestamp": time.time()
            }
            self.action_history.append(action_record)
            
            self.logger.log_action({
                "action": "wait_executed",
                **action_record
            })
            
            return True
        
        except Exception as e:
            self.logger.log_action({
                "action": "wait_failed",
                "duration": duration,
                "error": str(e)
            })
            return False
    
    def move(self, x: int, y: int, duration: float = 0.2) -> bool:
        """
        Déplace la souris vers une position.
        
        Args:
            x: Coordonnée X
            y: Coordonnée Y
            duration: Durée du mouvement en secondes
            
        Returns:
            True si succès, False sinon
        """
        try:
            current_pos = pyautogui.position()
            
            pyautogui.moveTo(x, y, duration=duration)
            
            action_record = {
                "type": ActionType.MOVE.value,
                "x": x,
                "y": y,
                "previous_position": current_pos,
                "duration": duration,
                "timestamp": time.time()
            }
            self.action_history.append(action_record)
            
            self.logger.log_action({
                "action": "move_executed",
                **action_record
            })
            
            return True
        
        except Exception as e:
            self.logger.log_action({
                "action": "move_failed",
                "x": x,
                "y": y,
                "error": str(e)
            })
            return False
    
    def drag(
        self,
        start_x: int,
        start_y: int,
        end_x: int,
        end_y: int,
        duration: float = 0.5,
        button: str = "left"
    ) -> bool:
        """
        Effectue un glisser-déposer.
        
        Args:
            start_x: X de départ
            start_y: Y de départ
            end_x: X d'arrivée
            end_y: Y d'arrivée
            duration: Durée du glissement
            button: Bouton souris
            
        Returns:
            True si succès, False sinon
        """
        try:
            current_pos = pyautogui.position()
            
            pyautogui.moveTo(start_x, start_y)
            pyautogui.drag(end_x - start_x, end_y - start_y, duration=duration, button=button)
            
            action_record = {
                "type": ActionType.DRAG.value,
                "start_x": start_x,
                "start_y": start_y,
                "end_x": end_x,
                "end_y": end_y,
                "previous_position": current_pos,
                "duration": duration,
                "button": button,
                "timestamp": time.time()
            }
            self.action_history.append(action_record)
            
            self.logger.log_action({
                "action": "drag_executed",
                **action_record
            })
            
            return True
        
        except Exception as e:
            self.logger.log_action({
                "action": "drag_failed",
                "start": (start_x, start_y),
                "end": (end_x, end_y),
                "error": str(e)
            })
            return False
    
    def execute_inverse_action(self, action: Dict[str, Any]) -> bool:
        """
        Exécute l'action inverse pour rollback.
        
        Args:
            action: Action à inverser
            
        Returns:
            True si succès, False sinon
        """
        inverse = self.get_inverse_action(action)
        if not inverse:
            return False
        
        action_type = inverse.get("type")
        
        if action_type == ActionType.MOVE.value:
            return self.move(inverse["x"], inverse["y"], inverse.get("duration", 0.2))
        elif action_type == ActionType.SCROLL.value:
            return self.scroll(
                inverse["direction"],
                inverse["amount"],
                inverse.get("x"),
                inverse.get("y")
            )
        elif action_type == ActionType.DRAG.value:
            return self.drag(
                inverse["start_x"],
                inverse["start_y"],
                inverse["end_x"],
                inverse["end_y"],
                inverse.get("duration", 0.5),
                inverse.get("button", "left")
            )
        elif action_type == "press_key":
            # Exécuter les suppressions
            for _ in range(inverse.get("presses", 0)):
                pyautogui.press("backspace")
            return True
        
        return False
    
    def get_inverse_action(self, action: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """
        Génère l'action inverse pour rollback.
        
        Args:
            action: Action à inverser
            
        Returns:
            Action inverse ou None si non inversible
        """
        action_type = action.get("type")
        
        if action_type == ActionType.CLICK.value:
            # Un clic n'est pas vraiment inversible
            # On peut retourner à la position précédente
            prev_pos = action.get("previous_position")
            if prev_pos:
                return {
                    "type": ActionType.MOVE.value,
                    "x": prev_pos[0],
                    "y": prev_pos[1],
                    "duration": 0.2
                }
        
        elif action_type == ActionType.TYPE.value:
            # Inverser la saisie = supprimer le texte
            text_length = action.get("length", 0)
            return {
                "type": "press_key",
                "key": "backspace",
                "presses": text_length
            }
        
        elif action_type == ActionType.SCROLL.value:
            # Inverser le défilement
            direction = action.get("direction")
            amount = action.get("amount")
            inverse_direction = {
                "up": "down",
                "down": "up",
                "left": "right",
                "right": "left"
            }.get(direction)
            
            return {
                "type": ActionType.SCROLL.value,
                "direction": inverse_direction,
                "amount": amount,
                "x": action.get("x"),
                "y": action.get("y")
            }
        
        elif action_type == ActionType.MOVE.value:
            # Retourner à la position précédente
            prev_pos = action.get("previous_position")
            if prev_pos:
                return {
                    "type": ActionType.MOVE.value,
                    "x": prev_pos[0],
                    "y": prev_pos[1],
                    "duration": 0.2
                }
        
        elif action_type == ActionType.DRAG.value:
            # Inverser le glissement
            return {
                "type": ActionType.DRAG.value,
                "start_x": action.get("end_x"),
                "start_y": action.get("end_y"),
                "end_x": action.get("start_x"),
                "end_y": action.get("start_y"),
                "duration": action.get("duration", 0.5),
                "button": action.get("button", "left")
            }
        
        elif action_type == ActionType.WAIT.value:
            # L'attente n'a pas d'inverse
            return None
        
        return None
    
    def _convert_to_azerty(self, text: str) -> str:
        """
        Convertit du texte pour clavier AZERTY.
        
        Args:
            text: Texte à convertir
            
        Returns:
            Texte converti
        """
        # Pour l'instant, retourner tel quel
        # PyAutoGUI gère déjà le layout clavier du système
        # Cette méthode peut être étendue si nécessaire
        return text
    
    def get_action_history(self, limit: int = 50) -> list:
        """
        Retourne l'historique des actions.
        
        Args:
            limit: Nombre maximum d'actions à retourner
            
        Returns:
            Liste des dernières actions
        """
        return self.action_history[-limit:]
    
    def clear_history(self):
        """Efface l'historique des actions."""
        self.action_history = []
        self.logger.log_action({
            "action": "action_history_cleared"
        })
    
    def execute_action(self, action_data: Dict[str, Any]) -> bool:
        """
        Exécute une action depuis un dictionnaire de données.
        
        Args:
            action_data: Données de l'action à exécuter
                {
                    "action_type": str,
                    "bbox": (x, y, w, h),
                    "parameters": dict
                }
        
        Returns:
            True si succès, False sinon
        """
        action_type = action_data.get("action_type", "").lower()
        bbox = action_data.get("bbox", (0, 0, 0, 0))
        params = action_data.get("parameters", {})
        
        # Calculer le centre de la bbox pour les actions de clic
        x, y, w, h = bbox
        center_x = x + w // 2
        center_y = y + h // 2
        
        if action_type == "click":
            button = params.get("button", "left")
            clicks = params.get("clicks", 1)
            return self.click(center_x, center_y, button=button, clicks=clicks)
        
        elif action_type == "double_click":
            return self.click(center_x, center_y, clicks=2)
        
        elif action_type == "right_click":
            return self.click(center_x, center_y, button="right")
        
        elif action_type == "type":
            text = params.get("text", "")
            interval = params.get("interval", 0.0)
            return self.type_text(text, interval=interval)
        
        elif action_type == "scroll":
            direction = params.get("direction", "down")
            amount = params.get("amount", 3)
            return self.scroll(direction, amount, center_x, center_y)
        
        elif action_type == "wait":
            duration = params.get("duration", 1.0)
            return self.wait(duration)
        
        elif action_type == "move":
            duration = params.get("duration", 0.2)
            return self.move(center_x, center_y, duration=duration)
        
        elif action_type == "drag":
            end_bbox = params.get("end_bbox", bbox)
            end_x, end_y, end_w, end_h = end_bbox
            end_center_x = end_x + end_w // 2
            end_center_y = end_y + end_h // 2
            duration = params.get("duration", 0.5)
            button = params.get("button", "left")
            return self.drag(center_x, center_y, end_center_x, end_center_y, duration, button)
        
        else:
            self.logger.log_action({
                "action": "unknown_action_type",
                "action_type": action_type
            })
            return False
