"""Command pattern implementation for undo/redo functionality in hosts management.

This module provides the command pattern infrastructure for tracking and reversing
edit operations on hosts files, enabling comprehensive undo/redo functionality.
"""

from abc import ABC, abstractmethod
from typing import Any, Dict, Optional, TYPE_CHECKING
from dataclasses import dataclass

if TYPE_CHECKING:
    from .models import HostsFile, HostEntry


@dataclass
class OperationResult:
    """Result of executing or undoing a command.
    
    Attributes:
        success: Whether the operation succeeded
        message: Human-readable description of the result
        data: Optional additional data about the operation
    """
    success: bool
    message: str
    data: Optional[Dict[str, Any]] = None


class Command(ABC):
    """Abstract base class for all edit commands.
    
    All edit operations (toggle, move, add, delete, update) implement this interface
    to provide consistent execute/undo capabilities for the undo/redo system.
    """
    
    @abstractmethod
    def execute(self, hosts_file: "HostsFile") -> OperationResult:
        """Execute the command and return the result.
        
        Args:
            hosts_file: The hosts file to operate on
            
        Returns:
            OperationResult indicating success/failure and details
        """
        pass
    
    @abstractmethod
    def undo(self, hosts_file: "HostsFile") -> OperationResult:
        """Undo the command and return the result.
        
        Args:
            hosts_file: The hosts file to operate on
            
        Returns:
            OperationResult indicating success/failure and details
        """
        pass
    
    @abstractmethod
    def get_description(self) -> str:
        """Get a human-readable description of the command.
        
        Returns:
            String description of what this command does
        """
        pass


class UndoRedoHistory:
    """Manages undo/redo history with configurable limits.
    
    This class maintains separate stacks for undo and redo operations,
    executes commands while managing history, and provides methods to
    check availability of undo/redo operations.
    """
    
    def __init__(self, max_history: int = 50):
        """Initialize the history manager.
        
        Args:
            max_history: Maximum number of commands to keep in history
        """
        self.max_history = max_history
        self.undo_stack: list[Command] = []
        self.redo_stack: list[Command] = []
    
    def execute_command(self, command: Command, hosts_file: "HostsFile") -> OperationResult:
        """Execute a command and add it to the undo stack.
        
        Args:
            command: Command to execute
            hosts_file: The hosts file to operate on
            
        Returns:
            OperationResult from command execution
        """
        result = command.execute(hosts_file)
        
        if result.success:
            # Add to undo stack and clear redo stack
            self.undo_stack.append(command)
            self.redo_stack.clear()
            
            # Enforce history limit
            if len(self.undo_stack) > self.max_history:
                self.undo_stack.pop(0)
        
        return result
    
    def undo(self, hosts_file: "HostsFile") -> OperationResult:
        """Undo the last command.
        
        Args:
            hosts_file: The hosts file to operate on
            
        Returns:
            OperationResult from undo operation
        """
        if not self.can_undo():
            return OperationResult(
                success=False,
                message="No operations to undo"
            )
        
        command = self.undo_stack.pop()
        result = command.undo(hosts_file)
        
        if result.success:
            # Move command to redo stack
            self.redo_stack.append(command)
            
            # Enforce history limit on redo stack too
            if len(self.redo_stack) > self.max_history:
                self.redo_stack.pop(0)
        else:
            # If undo failed, put command back on undo stack
            self.undo_stack.append(command)
        
        return result
    
    def redo(self, hosts_file: "HostsFile") -> OperationResult:
        """Redo the last undone command.
        
        Args:
            hosts_file: The hosts file to operate on
            
        Returns:
            OperationResult from redo operation
        """
        if not self.can_redo():
            return OperationResult(
                success=False,
                message="No operations to redo"
            )
        
        command = self.redo_stack.pop()
        result = command.execute(hosts_file)
        
        if result.success:
            # Move command back to undo stack
            self.undo_stack.append(command)
        else:
            # If redo failed, put command back on redo stack
            self.redo_stack.append(command)
        
        return result
    
    def can_undo(self) -> bool:
        """Check if undo is possible.
        
        Returns:
            True if there are commands that can be undone
        """
        return len(self.undo_stack) > 0
    
    def can_redo(self) -> bool:
        """Check if redo is possible.
        
        Returns:
            True if there are commands that can be redone
        """
        return len(self.redo_stack) > 0
    
    def clear_history(self) -> None:
        """Clear both undo and redo stacks."""
        self.undo_stack.clear()
        self.redo_stack.clear()
    
    def get_undo_description(self) -> Optional[str]:
        """Get description of the next command that would be undone.
        
        Returns:
            Description string or None if no undo available
        """
        if self.can_undo():
            return self.undo_stack[-1].get_description()
        return None
    
    def get_redo_description(self) -> Optional[str]:
        """Get description of the next command that would be redone.
        
        Returns:
            Description string or None if no redo available
        """
        if self.can_redo():
            return self.redo_stack[-1].get_description()
        return None


class ToggleEntryCommand(Command):
    """Command to toggle an entry's active state."""
    
    def __init__(self, index: int):
        """Initialize the toggle command.
        
        Args:
            index: Index of the entry to toggle
        """
        self.index = index
        self.original_state: Optional[bool] = None
    
    def execute(self, hosts_file: "HostsFile") -> OperationResult:
        """Toggle the entry's active state."""
        if self.index < 0 or self.index >= len(hosts_file.entries):
            return OperationResult(
                success=False,
                message=f"Invalid entry index: {self.index}"
            )
        
        entry = hosts_file.entries[self.index]
        self.original_state = entry.is_active
        entry.is_active = not entry.is_active
        
        action = "activated" if entry.is_active else "deactivated"
        return OperationResult(
            success=True,
            message=f"Entry {action}: {entry.ip_address} {' '.join(entry.hostnames)}",
            data={"index": self.index, "new_state": entry.is_active}
        )
    
    def undo(self, hosts_file: "HostsFile") -> OperationResult:
        """Restore the entry's original active state."""
        if self.original_state is None:
            return OperationResult(
                success=False,
                message="Cannot undo: original state not saved"
            )
        
        if self.index < 0 or self.index >= len(hosts_file.entries):
            return OperationResult(
                success=False,
                message=f"Cannot undo: invalid entry index: {self.index}"
            )
        
        entry = hosts_file.entries[self.index]
        entry.is_active = self.original_state
        
        action = "activated" if entry.is_active else "deactivated"
        return OperationResult(
            success=True,
            message=f"Undid toggle: entry {action}",
            data={"index": self.index, "restored_state": entry.is_active}
        )
    
    def get_description(self) -> str:
        """Get description of this command."""
        return f"Toggle entry at index {self.index}"


class MoveEntryCommand(Command):
    """Command to move an entry up or down."""
    
    def __init__(self, from_index: int, to_index: int):
        """Initialize the move command.
        
        Args:
            from_index: Original position of the entry
            to_index: Target position for the entry
        """
        self.from_index = from_index
        self.to_index = to_index
    
    def execute(self, hosts_file: "HostsFile") -> OperationResult:
        """Move the entry from one position to another."""
        if (self.from_index < 0 or self.from_index >= len(hosts_file.entries) or
            self.to_index < 0 or self.to_index >= len(hosts_file.entries)):
            return OperationResult(
                success=False,
                message=f"Invalid move: from {self.from_index} to {self.to_index}"
            )
        
        if self.from_index == self.to_index:
            return OperationResult(
                success=True,
                message="No movement needed",
                data={"from_index": self.from_index, "to_index": self.to_index}
            )
        
        # Move the entry
        entry = hosts_file.entries.pop(self.from_index)
        hosts_file.entries.insert(self.to_index, entry)
        
        direction = "up" if self.to_index < self.from_index else "down"
        return OperationResult(
            success=True,
            message=f"Moved entry {direction}: {entry.ip_address} {' '.join(entry.hostnames)}",
            data={"from_index": self.from_index, "to_index": self.to_index, "direction": direction}
        )
    
    def undo(self, hosts_file: "HostsFile") -> OperationResult:
        """Move the entry back to its original position."""
        if (self.to_index < 0 or self.to_index >= len(hosts_file.entries) or
            self.from_index < 0 or self.from_index >= len(hosts_file.entries)):
            return OperationResult(
                success=False,
                message="Cannot undo move: invalid indices"
            )
        
        # Move back: from to_index back to from_index
        entry = hosts_file.entries.pop(self.to_index)
        hosts_file.entries.insert(self.from_index, entry)
        
        direction = "down" if self.to_index < self.from_index else "up"
        return OperationResult(
            success=True,
            message=f"Undid move: moved entry {direction}",
            data={"restored_index": self.from_index}
        )
    
    def get_description(self) -> str:
        """Get description of this command."""
        direction = "up" if self.to_index < self.from_index else "down"
        return f"Move entry {direction} (from {self.from_index} to {self.to_index})"


class AddEntryCommand(Command):
    """Command to add a new entry."""
    
    def __init__(self, entry: "HostEntry", index: Optional[int] = None):
        """Initialize the add command.
        
        Args:
            entry: The entry to add
            index: Position to insert at (None for end)
        """
        self.entry = entry
        self.index = index
        self.actual_index: Optional[int] = None
    
    def execute(self, hosts_file: "HostsFile") -> OperationResult:
        """Add the entry to the hosts file."""
        if self.index is None:
            # Add at the end
            hosts_file.entries.append(self.entry)
            self.actual_index = len(hosts_file.entries) - 1
        else:
            # Insert at specific position
            if self.index < 0 or self.index > len(hosts_file.entries):
                return OperationResult(
                    success=False,
                    message=f"Invalid insertion index: {self.index}"
                )
            hosts_file.entries.insert(self.index, self.entry)
            self.actual_index = self.index
        
        return OperationResult(
            success=True,
            message=f"Added entry: {self.entry.ip_address} {' '.join(self.entry.hostnames)}",
            data={"index": self.actual_index, "entry": self.entry}
        )
    
    def undo(self, hosts_file: "HostsFile") -> OperationResult:
        """Remove the added entry."""
        if self.actual_index is None:
            return OperationResult(
                success=False,
                message="Cannot undo: entry index not recorded"
            )
        
        if self.actual_index < 0 or self.actual_index >= len(hosts_file.entries):
            return OperationResult(
                success=False,
                message=f"Cannot undo: invalid entry index: {self.actual_index}"
            )
        
        # Verify we're removing the right entry
        entry_to_remove = hosts_file.entries[self.actual_index]
        if (entry_to_remove.ip_address != self.entry.ip_address or 
            entry_to_remove.hostnames != self.entry.hostnames):
            return OperationResult(
                success=False,
                message="Cannot undo: entry at index doesn't match added entry"
            )
        
        hosts_file.entries.pop(self.actual_index)
        
        return OperationResult(
            success=True,
            message=f"Undid add: removed entry {self.entry.ip_address}",
            data={"removed_index": self.actual_index}
        )
    
    def get_description(self) -> str:
        """Get description of this command."""
        return f"Add entry: {self.entry.ip_address} {' '.join(self.entry.hostnames)}"


class DeleteEntryCommand(Command):
    """Command to delete an entry."""
    
    def __init__(self, index: int):
        """Initialize the delete command.
        
        Args:
            index: Index of the entry to delete
        """
        self.index = index
        self.deleted_entry: Optional["HostEntry"] = None
    
    def execute(self, hosts_file: "HostsFile") -> OperationResult:
        """Delete the entry from the hosts file."""
        if self.index < 0 or self.index >= len(hosts_file.entries):
            return OperationResult(
                success=False,
                message=f"Invalid entry index: {self.index}"
            )
        
        self.deleted_entry = hosts_file.entries.pop(self.index)
        
        return OperationResult(
            success=True,
            message=f"Deleted entry: {self.deleted_entry.ip_address} {' '.join(self.deleted_entry.hostnames)}",
            data={"index": self.index, "deleted_entry": self.deleted_entry}
        )
    
    def undo(self, hosts_file: "HostsFile") -> OperationResult:
        """Restore the deleted entry."""
        if self.deleted_entry is None:
            return OperationResult(
                success=False,
                message="Cannot undo: deleted entry not saved"
            )
        
        if self.index < 0 or self.index > len(hosts_file.entries):
            return OperationResult(
                success=False,
                message=f"Cannot undo: invalid restoration index: {self.index}"
            )
        
        hosts_file.entries.insert(self.index, self.deleted_entry)
        
        return OperationResult(
            success=True,
            message=f"Undid delete: restored entry {self.deleted_entry.ip_address}",
            data={"restored_index": self.index, "restored_entry": self.deleted_entry}
        )
    
    def get_description(self) -> str:
        """Get description of this command."""
        if self.deleted_entry:
            return f"Delete entry: {self.deleted_entry.ip_address} {' '.join(self.deleted_entry.hostnames)}"
        return f"Delete entry at index {self.index}"


class UpdateEntryCommand(Command):
    """Command to update an entry."""
    
    def __init__(self, index: int, new_ip: str, new_hostnames: list[str], 
                 new_comment: Optional[str], new_active: bool):
        """Initialize the update command.
        
        Args:
            index: Index of the entry to update
            new_ip: New IP address
            new_hostnames: New list of hostnames
            new_comment: New comment (optional)
            new_active: New active state
        """
        self.index = index
        self.new_ip = new_ip
        self.new_hostnames = new_hostnames.copy()  # Make a copy to avoid mutation
        self.new_comment = new_comment
        self.new_active = new_active
        self.original_entry: Optional["HostEntry"] = None
    
    def execute(self, hosts_file: "HostsFile") -> OperationResult:
        """Update the entry with new values."""
        if self.index < 0 or self.index >= len(hosts_file.entries):
            return OperationResult(
                success=False,
                message=f"Invalid entry index: {self.index}"
            )
        
        # Save original entry for undo
        from .models import HostEntry
        original = hosts_file.entries[self.index]
        self.original_entry = HostEntry(
            ip_address=original.ip_address,
            hostnames=original.hostnames.copy(),
            comment=original.comment,
            is_active=original.is_active
        )
        
        # Update the entry
        entry = hosts_file.entries[self.index]
        entry.ip_address = self.new_ip
        entry.hostnames = self.new_hostnames.copy()
        entry.comment = self.new_comment
        entry.is_active = self.new_active
        
        return OperationResult(
            success=True,
            message=f"Updated entry: {entry.ip_address} {' '.join(entry.hostnames)}",
            data={"index": self.index, "updated_entry": entry}
        )
    
    def undo(self, hosts_file: "HostsFile") -> OperationResult:
        """Restore the entry's original values."""
        if self.original_entry is None:
            return OperationResult(
                success=False,
                message="Cannot undo: original entry not saved"
            )
        
        if self.index < 0 or self.index >= len(hosts_file.entries):
            return OperationResult(
                success=False,
                message=f"Cannot undo: invalid entry index: {self.index}"
            )
        
        # Restore original values
        entry = hosts_file.entries[self.index]
        entry.ip_address = self.original_entry.ip_address
        entry.hostnames = self.original_entry.hostnames.copy()
        entry.comment = self.original_entry.comment
        entry.is_active = self.original_entry.is_active
        
        return OperationResult(
            success=True,
            message=f"Undid update: restored entry {entry.ip_address}",
            data={"index": self.index, "restored_entry": entry}
        )
    
    def get_description(self) -> str:
        """Get description of this command."""
        if self.original_entry:
            return f"Update entry: {self.original_entry.ip_address} → {self.new_ip}"
        return f"Update entry at index {self.index}"
