"""Base mixin providing shared ADB command execution."""

import asyncio
import contextlib
import shlex

from fastmcp.contrib.mcp_mixin import MCPMixin

from ..models import CommandResult

# Default timeout for ADB commands (30 seconds)
DEFAULT_TIMEOUT = 30


class ADBBaseMixin(MCPMixin):
    """Base mixin with shared ADB functionality.

    Provides:
    - Async ADB command execution
    - Device targeting (multi-device support)
    - Command result parsing
    """

    def __init__(self) -> None:
        super().__init__()
        self._current_device: str | None = None

    def set_current_device(self, device_id: str | None) -> None:
        """Set the default device for subsequent commands."""
        self._current_device = device_id

    def get_current_device(self) -> str | None:
        """Get the current default device."""
        return self._current_device

    async def run_adb(
        self,
        cmd: list[str],
        device_id: str | None = None,
        timeout: int = DEFAULT_TIMEOUT,
    ) -> CommandResult:
        """Execute ADB command and return structured result.

        Args:
            cmd: Command arguments (without 'adb' prefix)
            device_id: Target device (uses current if not specified)
            timeout: Command timeout in seconds (default 30)

        Returns:
            CommandResult with success status, stdout, stderr, returncode
        """
        full_cmd = ["adb"]

        # Use specified device, fall back to current device
        target_device = device_id or self._current_device
        if target_device:
            full_cmd.extend(["-s", target_device])

        full_cmd.extend(cmd)

        try:
            result = await asyncio.create_subprocess_exec(
                *full_cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
            )

            stdout, stderr = await asyncio.wait_for(
                result.communicate(), timeout=timeout
            )

            return CommandResult(
                success=result.returncode == 0,
                stdout=stdout.decode("utf-8", errors="ignore").strip(),
                stderr=stderr.decode("utf-8", errors="ignore").strip(),
                returncode=result.returncode or 0,
            )
        except TimeoutError:
            # Kill the process if it timed out
            with contextlib.suppress(ProcessLookupError):
                result.kill()  # type: ignore[possibly-undefined]
            return CommandResult(
                success=False,
                stdout="",
                stderr=f"Command timed out after {timeout}s",
                returncode=-1,
            )
        except Exception as e:
            return CommandResult(
                success=False,
                stdout="",
                stderr=str(e),
                returncode=-1,
            )

    async def run_shell(
        self,
        command: str,
        device_id: str | None = None,
        timeout: int = DEFAULT_TIMEOUT,
    ) -> CommandResult:
        """Execute shell command on device (string form).

        WARNING: Only use for developer-mode shell_command where the
        user explicitly provides the command string. For structured
        commands with known arguments, use run_shell_args() instead
        to prevent shell injection.

        Args:
            command: Shell command string
            device_id: Target device
            timeout: Command timeout in seconds

        Returns:
            CommandResult with command output
        """
        # Use shlex to properly handle quoted strings
        try:
            parts = shlex.split(command)
        except ValueError:
            # Fall back to simple split if shlex fails
            parts = command.split()

        return await self.run_adb(["shell"] + parts, device_id, timeout=timeout)

    async def run_shell_args(
        self,
        args: list[str],
        device_id: str | None = None,
        timeout: int = DEFAULT_TIMEOUT,
    ) -> CommandResult:
        """Execute shell command on device (list form, injection-safe).

        Each argument is shell-quoted before being sent to the device,
        preventing shell injection even though ADB concatenates args
        for the device-side shell interpreter.

        Args:
            args: Command arguments as a list
            device_id: Target device
            timeout: Command timeout in seconds

        Returns:
            CommandResult with command output
        """
        # Quote each arg for the device-side shell. ADB concatenates
        # all args after "shell" with spaces and sends to the device
        # shell, so we must quote to prevent injection.
        quoted = [shlex.quote(a) for a in args]
        return await self.run_adb(["shell"] + quoted, device_id, timeout=timeout)

    async def get_device_property(
        self,
        prop: str,
        device_id: str | None = None,
    ) -> str | None:
        """Get a device property via getprop.

        Args:
            prop: Property name (e.g., 'ro.product.model')
            device_id: Target device

        Returns:
            Property value or None if not found
        """
        result = await self.run_shell_args(["getprop", prop], device_id)
        if result.success and result.stdout:
            return result.stdout
        return None
