"""
Manager for hosts file edit operations.

This module handles permission management, edit mode operations,
and safe file modifications with backup and validation.
"""

import os
import subprocess
import tempfile
from pathlib import Path
from typing import Optional, Tuple
from .models import HostEntry, HostsFile
from .parser import HostsParser
from .commands import (
    UndoRedoHistory,
    ToggleEntryCommand,
    MoveEntryCommand,
    AddEntryCommand,
    DeleteEntryCommand,
    UpdateEntryCommand,
    OperationResult,
)

class PermissionManager:
    """
    Manages sudo permissions for hosts file editing.

    Handles requesting, validating, and releasing elevated permissions
    needed for modifying the system hosts file.
    """

    def __init__(self):
        self.has_sudo = False
        self._sudo_validated = False

    def request_sudo(self, password: str = None) -> Tuple[bool, str]:
        """
        Request sudo permissions for hosts file editing.

        Args:
            password: Optional password for sudo authentication

        Returns:
            Tuple of (success, message)
        """
        try:
            # Test sudo access with a simple command
            result = subprocess.run(
                ["sudo", "-n", "true"], capture_output=True, text=True, timeout=5
            )

            if result.returncode == 0:
                # Already have sudo access
                self.has_sudo = True
                self._sudo_validated = True
                return True, "Sudo access already available"

            # If no password provided, indicate we need password input
            if password is None:
                return False, "Password required for sudo access"

            # Use password for sudo authentication
            result = subprocess.run(
                ["sudo", "-S", "-v"],
                input=password + "\n",
                capture_output=True,
                text=True,
                timeout=10,
            )

            if result.returncode == 0:
                self.has_sudo = True
                self._sudo_validated = True
                return True, "Sudo access granted"
            else:
                # Check if it's a password error
                if (
                    "incorrect password" in result.stderr.lower()
                    or "authentication failure" in result.stderr.lower()
                ):
                    return False, "Incorrect password"
                else:
                    return False, f"Sudo access denied: {result.stderr}"

        except subprocess.TimeoutExpired:
            return False, "Sudo request timed out"
        except Exception as e:
            return False, f"Error requesting sudo: {e}"

    def validate_permissions(self, file_path: str = "/etc/hosts") -> bool:
        """
        Validate that we have write permissions to the hosts file.

        Args:
            file_path: Path to the hosts file

        Returns:
            True if we can write to the file
        """
        if not self.has_sudo:
            return False

        try:
            # Test write access with sudo
            result = subprocess.run(
                ["sudo", "-n", "test", "-w", file_path], capture_output=True, timeout=5
            )
            return result.returncode == 0
        except Exception:
            return False

    def release_sudo(self) -> None:
        """Release sudo permissions."""
        try:
            subprocess.run(["sudo", "-k"], capture_output=True, timeout=5)
        except Exception:
            pass
        finally:
            self.has_sudo = False
            self._sudo_validated = False

class HostsManager:
    """
    Main manager for hosts file edit operations.

    Provides high-level operations for modifying hosts file entries
    with proper permission management, validation, and backup.
    """

    def __init__(self, file_path: str = "/etc/hosts"):
        self.parser = HostsParser(file_path)
        self.permission_manager = PermissionManager()
        self.edit_mode = False
        self._backup_path: Optional[Path] = None
        self.undo_redo_history = UndoRedoHistory()

    def enter_edit_mode(self, password: str = None) -> Tuple[bool, str]:
        """
        Enter edit mode with proper permission management.

        Args:
            password: Optional password for sudo authentication

        Returns:
            Tuple of (success, message)
        """
        if self.edit_mode:
            return True, "Already in edit mode"

        # Request sudo permissions
        success, message = self.permission_manager.request_sudo(password)
        if not success:
            return False, message

        # Validate write permissions
        if not self.permission_manager.validate_permissions(str(self.parser.file_path)):
            return False, "Cannot write to hosts file even with sudo"

        # Create backup
        try:
            self._create_backup()
            self.edit_mode = True
            return True, "Edit mode enabled"
        except Exception as e:
            return False, f"Failed to create backup: {e}"

    def exit_edit_mode(self) -> Tuple[bool, str]:
        """
        Exit edit mode and release permissions.

        Returns:
            Tuple of (success, message)
        """
        if not self.edit_mode:
            return True, "Already in read-only mode"

        try:
            self.permission_manager.release_sudo()
            self.edit_mode = False
            self._backup_path = None
            self.undo_redo_history.clear_history()  # Clear undo/redo history when exiting edit mode
            return True, "Edit mode disabled"
        except Exception as e:
            return False, f"Error exiting edit mode: {e}"

    def toggle_entry(self, hosts_file: HostsFile, index: int) -> Tuple[bool, str]:
        """
        Toggle the active state of an entry.

        Args:
            hosts_file: The hosts file to modify
            index: Index of the entry to toggle

        Returns:
            Tuple of (success, message)
        """
        if not self.edit_mode:
            return False, "Not in edit mode"

        if not (0 <= index < len(hosts_file.entries)):
            return False, "Invalid entry index"

        try:
            entry = hosts_file.entries[index]

            # Prevent modification of default system entries
            if entry.is_default_entry():
                return False, "Cannot modify default system entries"

            old_state = "active" if entry.is_active else "inactive"
            entry.is_active = not entry.is_active
            new_state = "active" if entry.is_active else "inactive"

            return True, f"Entry toggled from {old_state} to {new_state}"
        except Exception as e:
            return False, f"Error toggling entry: {e}"

    def move_entry_up(self, hosts_file: HostsFile, index: int) -> Tuple[bool, str]:
        """
        Move an entry up in the list.

        Args:
            hosts_file: The hosts file to modify
            index: Index of the entry to move

        Returns:
            Tuple of (success, message)
        """
        if not self.edit_mode:
            return False, "Not in edit mode"

        if index <= 0 or index >= len(hosts_file.entries):
            return False, "Cannot move entry up"

        try:
            entry = hosts_file.entries[index]
            target_entry = hosts_file.entries[index - 1]

            # Prevent moving default system entries or moving entries above default entries
            if entry.is_default_entry() or target_entry.is_default_entry():
                return False, "Cannot move default system entries"

            # Swap with previous entry
            hosts_file.entries[index], hosts_file.entries[index - 1] = (
                hosts_file.entries[index - 1],
                hosts_file.entries[index],
            )
            return True, "Entry moved up"
        except Exception as e:
            return False, f"Error moving entry: {e}"

    def move_entry_down(self, hosts_file: HostsFile, index: int) -> Tuple[bool, str]:
        """
        Move an entry down in the list.

        Args:
            hosts_file: The hosts file to modify
            index: Index of the entry to move

        Returns:
            Tuple of (success, message)
        """
        if not self.edit_mode:
            return False, "Not in edit mode"

        if index < 0 or index >= len(hosts_file.entries) - 1:
            return False, "Cannot move entry down"

        try:
            entry = hosts_file.entries[index]
            target_entry = hosts_file.entries[index + 1]

            # Prevent moving default system entries or moving entries below default entries
            if entry.is_default_entry() or target_entry.is_default_entry():
                return False, "Cannot move default system entries"

            # Swap with next entry
            hosts_file.entries[index], hosts_file.entries[index + 1] = (
                hosts_file.entries[index + 1],
                hosts_file.entries[index],
            )
            return True, "Entry moved down"
        except Exception as e:
            return False, f"Error moving entry: {e}"

    def add_entry(
        self,
        hosts_file: HostsFile,
        entry: HostEntry,
    ) -> Tuple[bool, str]:
        """
        Add a new entry to the hosts file.

        Args:
            hosts_file: The hosts file to modify
            entry: The new entry to add

        Returns:
            Tuple of (success, message)
        """
        if not self.edit_mode:
            return False, "Not in edit mode"

        try:
            # Add the new entry at the end
            hosts_file.entries.append(entry)

            # Save the file immediately
            save_success, save_message = self.save_hosts_file(hosts_file)
            if not save_success:
                # If save fails, remove the entry that was just added
                hosts_file.entries.pop()
                return False, f"Failed to save after adding entry: {save_message}"

            canonical_hostname = (
                entry.hostnames[0] if entry.hostnames else entry.ip_address
            )
            return True, f"Entry added: {canonical_hostname}"

        except Exception as e:
            return False, f"Error adding entry: {e}"

    def delete_entry(
        self,
        hosts_file: HostsFile,
        index: int,
    ) -> Tuple[bool, str]:
        """
        Delete an entry from the hosts file.

        Args:
            hosts_file: The hosts file to modify
            index: Index of the entry to delete

        Returns:
            Tuple of (success, message)
        """
        if not self.edit_mode:
            return False, "Not in edit mode"

        if not (0 <= index < len(hosts_file.entries)):
            return False, "Invalid entry index"

        try:
            entry = hosts_file.entries[index]

            # Prevent deletion of default system entries
            if entry.is_default_entry():
                return False, "Cannot delete default system entries"

            # Remove the entry
            deleted_entry = hosts_file.entries.pop(index)
            canonical_hostname = (
                deleted_entry.hostnames[0]
                if deleted_entry.hostnames
                else deleted_entry.ip_address
            )

            # Save the file immediately
            save_success, save_message = self.save_hosts_file(hosts_file)
            if not save_success:
                # If save fails, restore the entry
                hosts_file.entries.insert(index, deleted_entry)
                return False, f"Failed to save after deletion: {save_message}"

            return True, f"Entry deleted: {canonical_hostname}"

        except Exception as e:
            return False, f"Error deleting entry: {e}"

    def update_entry(
        self,
        hosts_file: HostsFile,
        index: int,
        ip_address: str,
        hostnames: list[str],
        comment: Optional[str] = None,
    ) -> Tuple[bool, str]:
        """
        Update an existing entry.

        Args:
            hosts_file: The hosts file to modify
            index: Index of the entry to update
            ip_address: New IP address
            hostnames: New list of hostnames
            comment: New comment (optional)

        Returns:
            Tuple of (success, message)
        """
        if not self.edit_mode:
            return False, "Not in edit mode"

        if not (0 <= index < len(hosts_file.entries)):
            return False, "Invalid entry index"

        try:
            entry = hosts_file.entries[index]

            # Prevent modification of default system entries
            if entry.is_default_entry():
                return False, "Cannot modify default system entries"

            # Create new entry to validate
            new_entry = HostEntry(
                ip_address=ip_address,
                hostnames=hostnames,
                comment=comment,
                is_active=hosts_file.entries[index].is_active,
                dns_name=hosts_file.entries[index].dns_name,
            )

            # Replace the entry
            hosts_file.entries[index] = new_entry
            return True, "Entry updated successfully"

        except ValueError as e:
            return False, f"Invalid entry data: {e}"
        except Exception as e:
            return False, f"Error updating entry: {e}"

    # Command-based methods for undo/redo functionality
    def execute_toggle_command(self, hosts_file: HostsFile, index: int) -> OperationResult:
        """
        Execute a toggle command with undo/redo support.

        Args:
            hosts_file: The hosts file to modify
            index: Index of the entry to toggle

        Returns:
            OperationResult with success status and message
        """
        if not self.edit_mode:
            return OperationResult(False, "Not in edit mode")

        command = ToggleEntryCommand(index)
        result = self.undo_redo_history.execute_command(command, hosts_file)
        return result

    def execute_move_command(self, hosts_file: HostsFile, index: int, direction: str) -> OperationResult:
        """
        Execute a move command with undo/redo support.

        Args:
            hosts_file: The hosts file to modify
            index: Index of the entry to move
            direction: "up" or "down"

        Returns:
            OperationResult with success status and message
        """
        if not self.edit_mode:
            return OperationResult(False, "Not in edit mode")

        # Convert direction to target index
        if direction == "up":
            if index <= 0:
                return OperationResult(False, "Cannot move first entry up")
            to_index = index - 1
        elif direction == "down":
            if index >= len(hosts_file.entries) - 1:
                return OperationResult(False, "Cannot move last entry down")
            to_index = index + 1
        else:
            return OperationResult(False, f"Invalid direction: {direction}")

        command = MoveEntryCommand(index, to_index)
        result = self.undo_redo_history.execute_command(command, hosts_file)
        return result

    def execute_add_command(self, hosts_file: HostsFile, entry: HostEntry, save_callback=None) -> OperationResult:
        """
        Execute an add command with undo/redo support.

        Args:
            hosts_file: The hosts file to modify
            entry: The new entry to add
            save_callback: Optional callback to save the file after adding

        Returns:
            OperationResult with success status and message
        """
        if not self.edit_mode:
            return OperationResult(False, "Not in edit mode")

        command = AddEntryCommand(entry)
        result = self.undo_redo_history.execute_command(command, hosts_file)
        return result

    def execute_delete_command(self, hosts_file: HostsFile, index: int, save_callback=None) -> OperationResult:
        """
        Execute a delete command with undo/redo support.

        Args:
            hosts_file: The hosts file to modify
            index: Index of the entry to delete
            save_callback: Optional callback to save the file after deletion

        Returns:
            OperationResult with success status and message
        """
        if not self.edit_mode:
            return OperationResult(False, "Not in edit mode")

        command = DeleteEntryCommand(index)
        result = self.undo_redo_history.execute_command(command, hosts_file)
        return result

    def execute_update_command(self, hosts_file: HostsFile, index: int, ip_address: str, hostnames: list[str], comment: Optional[str], is_active: bool) -> OperationResult:
        """
        Execute an update command with undo/redo support.

        Args:
            hosts_file: The hosts file to modify
            index: Index of the entry to update
            ip_address: New IP address
            hostnames: New list of hostnames
            comment: New comment (optional)
            is_active: New active state

        Returns:
            OperationResult with success status and message
        """
        if not self.edit_mode:
            return OperationResult(False, "Not in edit mode")

        command = UpdateEntryCommand(index, ip_address, hostnames, comment, is_active)
        result = self.undo_redo_history.execute_command(command, hosts_file)
        return result

    def undo_last_operation(self, hosts_file: HostsFile) -> OperationResult:
        """
        Undo the last operation.

        Args:
            hosts_file: The hosts file to operate on

        Returns:
            OperationResult with success status and message
        """
        if not self.edit_mode:
            return OperationResult(False, "Not in edit mode")

        return self.undo_redo_history.undo(hosts_file)

    def redo_last_operation(self, hosts_file: HostsFile) -> OperationResult:
        """
        Redo the last undone operation.

        Args:
            hosts_file: The hosts file to operate on

        Returns:
            OperationResult with success status and message
        """
        if not self.edit_mode:
            return OperationResult(False, "Not in edit mode")

        return self.undo_redo_history.redo(hosts_file)

    def can_undo(self) -> bool:
        """Check if undo is available."""
        return self.undo_redo_history.can_undo()

    def can_redo(self) -> bool:
        """Check if redo is available."""
        return self.undo_redo_history.can_redo()

    def get_undo_description(self) -> Optional[str]:
        """Get description of the operation that would be undone."""
        return self.undo_redo_history.get_undo_description()

    def get_redo_description(self) -> Optional[str]:
        """Get description of the operation that would be redone."""
        return self.undo_redo_history.get_redo_description()

    def save_hosts_file(self, hosts_file: HostsFile) -> Tuple[bool, str]:
        """
        Save the hosts file to disk with sudo permissions.

        Args:
            hosts_file: The hosts file to save

        Returns:
            Tuple of (success, message)
        """
        if not self.edit_mode:
            return False, "Not in edit mode"

        if not self.permission_manager.has_sudo:
            return False, "No sudo permissions"

        try:
            # Serialize the hosts file
            content = self.parser.serialize(hosts_file)

            # Write to temporary file first
            with tempfile.NamedTemporaryFile(
                mode="w", delete=False, suffix=".hosts"
            ) as temp_file:
                temp_file.write(content)
                temp_path = temp_file.name

            try:
                # Use sudo to copy the temp file to the hosts file
                result = subprocess.run(
                    ["sudo", "cp", temp_path, str(self.parser.file_path)],
                    capture_output=True,
                    text=True,
                    timeout=10,
                )

                if result.returncode == 0:
                    return True, "Hosts file saved successfully"
                else:
                    return False, f"Failed to save hosts file: {result.stderr}"

            finally:
                # Clean up temp file
                try:
                    os.unlink(temp_path)
                except Exception:
                    pass

        except Exception as e:
            return False, f"Error saving hosts file: {e}"

    def restore_backup(self) -> Tuple[bool, str]:
        """
        Restore the hosts file from backup.

        Returns:
            Tuple of (success, message)
        """
        if not self.edit_mode:
            return False, "Not in edit mode"

        if not self._backup_path or not self._backup_path.exists():
            return False, "No backup available"

        try:
            result = subprocess.run(
                ["sudo", "cp", str(self._backup_path), str(self.parser.file_path)],
                capture_output=True,
                text=True,
                timeout=10,
            )

            if result.returncode == 0:
                return True, "Backup restored successfully"
            else:
                return False, f"Failed to restore backup: {result.stderr}"

        except Exception as e:
            return False, f"Error restoring backup: {e}"

    def _create_backup(self) -> None:
        """Create a backup of the current hosts file."""
        if not self.parser.file_path.exists():
            return

        # Create backup in temp directory
        backup_dir = Path(tempfile.gettempdir()) / "hosts-manager-backups"
        backup_dir.mkdir(exist_ok=True)

        import time

        timestamp = int(time.time())
        self._backup_path = backup_dir / f"hosts.backup.{timestamp}"

        # Copy current hosts file to backup
        result = subprocess.run(
            ["sudo", "cp", str(self.parser.file_path), str(self._backup_path)],
            capture_output=True,
            timeout=10,
        )

        if result.returncode != 0:
            raise Exception(f"Failed to create backup: {result.stderr}")

        # Make backup readable by user
        subprocess.run(
            ["sudo", "chmod", "644", str(self._backup_path)], capture_output=True
        )

class EditModeError(Exception):
    """Base exception for edit mode errors."""

    pass

class PermissionError(EditModeError):
    """Raised when there are permission issues."""

    pass

class ValidationError(EditModeError):
    """Raised when validation fails."""

    pass
