"""Input mixin for Android ADB MCP Server.

Provides tools for simulating user input on Android devices.
"""

import re
from typing import Any

from fastmcp.contrib.mcp_mixin import mcp_tool

from ..config import is_developer_mode
from .base import ADBBaseMixin

# Characters that ADB's input text command cannot handle — suggest clipboard
_INPUT_TEXT_UNSAFE = set("'\"\\`$(){}[]|&;<>!~#%^*?")


class InputMixin(ADBBaseMixin):
    """Mixin for Android input simulation.

    Provides tools for:
    - Tapping screen coordinates
    - Swiping/scrolling gestures
    - Key events (back, home, etc.)
    - Text input
    - Raw shell commands (developer mode)
    """

    async def _get_screen_dimensions(
        self,
        device_id: str | None = None,
    ) -> tuple[int, int]:
        """Get screen width and height, falling back to 1080x1920."""
        result = await self.run_shell_args(["wm", "size"], device_id)
        if result.success:
            # Parse "Physical size: 1080x1920" or "Override size: ..."
            match = re.search(r"(\d+)x(\d+)", result.stdout)
            if match:
                return int(match.group(1)), int(match.group(2))
        return 1080, 1920

    @mcp_tool()
    async def input_tap(
        self,
        x: int,
        y: int,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Tap at screen coordinates.

        Simulates a finger tap at the specified position.

        Args:
            x: X coordinate (pixels from left)
            y: Y coordinate (pixels from top)
            device_id: Target device (optional if one device or current set)

        Returns:
            Success status
        """
        result = await self.run_shell_args(["input", "tap", str(x), str(y)], device_id)
        return {
            "success": result.success,
            "action": "tap",
            "coordinates": {"x": x, "y": y},
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool()
    async def input_swipe(
        self,
        x1: int,
        y1: int,
        x2: int,
        y2: int,
        duration_ms: int = 300,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Swipe between two points.

        Simulates a finger swipe gesture. Use for scrolling, dragging, etc.

        Common patterns:
        - Scroll down: swipe from bottom to top (y1 > y2)
        - Scroll up: swipe from top to bottom (y1 < y2)
        - Swipe left: swipe from right to left (x1 > x2)
        - Swipe right: swipe from left to right (x1 < x2)

        Args:
            x1: Start X coordinate
            y1: Start Y coordinate
            x2: End X coordinate
            y2: End Y coordinate
            duration_ms: Swipe duration in milliseconds (default 300)
            device_id: Target device

        Returns:
            Success status
        """
        result = await self.run_shell_args(
            [
                "input",
                "swipe",
                str(x1),
                str(y1),
                str(x2),
                str(y2),
                str(duration_ms),
            ],
            device_id,
        )
        return {
            "success": result.success,
            "action": "swipe",
            "from": {"x": x1, "y": y1},
            "to": {"x": x2, "y": y2},
            "duration_ms": duration_ms,
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool()
    async def input_scroll_down(
        self,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Scroll down one page.

        Convenience method for common scroll-down gesture.
        Queries actual screen dimensions to compute center coordinates.

        Args:
            device_id: Target device

        Returns:
            Success status
        """
        w, h = await self._get_screen_dimensions(device_id)
        cx = w // 2
        # Swipe from 65% to 25% of screen height
        y_start = int(h * 0.65)
        y_end = int(h * 0.25)
        result = await self.run_shell_args(
            [
                "input",
                "swipe",
                str(cx),
                str(y_start),
                str(cx),
                str(y_end),
                "300",
            ],
            device_id,
        )
        return {
            "success": result.success,
            "action": "scroll_down",
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool()
    async def input_scroll_up(
        self,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Scroll up one page.

        Convenience method for common scroll-up gesture.
        Queries actual screen dimensions to compute center coordinates.

        Args:
            device_id: Target device

        Returns:
            Success status
        """
        w, h = await self._get_screen_dimensions(device_id)
        cx = w // 2
        # Swipe from 25% to 65% of screen height
        y_start = int(h * 0.25)
        y_end = int(h * 0.65)
        result = await self.run_shell_args(
            [
                "input",
                "swipe",
                str(cx),
                str(y_start),
                str(cx),
                str(y_end),
                "300",
            ],
            device_id,
        )
        return {
            "success": result.success,
            "action": "scroll_up",
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool()
    async def input_back(
        self,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Press the Back button.

        Simulates pressing the Android back button.

        Args:
            device_id: Target device

        Returns:
            Success status
        """
        result = await self.run_shell_args(
            ["input", "keyevent", "KEYCODE_BACK"], device_id
        )
        return {
            "success": result.success,
            "action": "back",
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool()
    async def input_home(
        self,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Press the Home button.

        Returns to the home screen.

        Args:
            device_id: Target device

        Returns:
            Success status
        """
        result = await self.run_shell_args(
            ["input", "keyevent", "KEYCODE_HOME"], device_id
        )
        return {
            "success": result.success,
            "action": "home",
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool()
    async def input_recent_apps(
        self,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Open recent apps / app switcher.

        Shows the recent applications overview.

        Args:
            device_id: Target device

        Returns:
            Success status
        """
        result = await self.run_shell_args(
            ["input", "keyevent", "KEYCODE_APP_SWITCH"], device_id
        )
        return {
            "success": result.success,
            "action": "recent_apps",
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool()
    async def input_key(
        self,
        key_code: str,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Send a key event.

        Send any Android key event by code name.

        Common key codes:
        - KEYCODE_BACK, KEYCODE_HOME, KEYCODE_APP_SWITCH
        - KEYCODE_VOLUME_UP, KEYCODE_VOLUME_DOWN, KEYCODE_MUTE
        - KEYCODE_POWER, KEYCODE_MENU, KEYCODE_SEARCH
        - KEYCODE_ENTER, KEYCODE_DEL (backspace), KEYCODE_TAB
        - KEYCODE_DPAD_UP/DOWN/LEFT/RIGHT, KEYCODE_DPAD_CENTER

        Args:
            key_code: Android key code (e.g., "KEYCODE_ENTER")
            device_id: Target device

        Returns:
            Success status
        """
        # Normalize key code — strip anything non-alphanumeric/underscore
        clean = re.sub(r"[^A-Za-z0-9_]", "", key_code)
        if not clean.startswith("KEYCODE_"):
            clean = f"KEYCODE_{clean.upper()}"

        result = await self.run_shell_args(["input", "keyevent", clean], device_id)
        return {
            "success": result.success,
            "action": "key",
            "key_code": clean,
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool()
    async def input_text(
        self,
        text: str,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Type text into the focused input field.

        Types the specified text as if entered via keyboard.
        Focus must be on a text input field first.

        Note: Only handles basic alphanumeric text and common punctuation.
        For text with special characters, use clipboard_set(text, paste=True)
        which handles all characters correctly.

        Args:
            text: Text to type (spaces are handled automatically)
            device_id: Target device

        Returns:
            Success status
        """
        # Check for characters that ADB input text can't handle
        has_unsafe = any(c in _INPUT_TEXT_UNSAFE for c in text)
        if has_unsafe:
            return {
                "success": False,
                "error": (
                    "Text contains special characters that ADB input "
                    "text cannot handle reliably. Use "
                    "clipboard_set(text, paste=True) instead."
                ),
                "text": text,
            }

        # ADB input text: spaces must be %s, no shell metacharacters
        escaped = text.replace(" ", "%s")
        result = await self.run_shell_args(["input", "text", escaped], device_id)
        return {
            "success": result.success,
            "action": "text",
            "text": text,
            "error": result.stderr if not result.success else None,
        }

    # === Developer Mode Tools ===

    @mcp_tool(
        tags={"developer"},
        annotations={"requires": "developer_mode"},
    )
    async def shell_command(
        self,
        command: str,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Execute arbitrary shell command on device.

        [DEVELOPER MODE] Run any shell command on the Android device.
        Use with caution - commands run with shell user permissions.

        Common commands:
        - ls /sdcard - list files
        - getprop ro.build.version.release - get Android version
        - pm list packages - list installed packages
        - dumpsys battery - battery info
        - settings get system screen_brightness - screen brightness

        Args:
            command: Shell command to execute
            device_id: Target device

        Returns:
            Command output with stdout, stderr, and return code
        """
        if not is_developer_mode():
            return {
                "success": False,
                "error": (
                    "Developer mode required. "
                    "Enable with config_set_developer_mode(True)"
                ),
            }

        # Developer shell_command intentionally uses run_shell (string form)
        # since the user explicitly provides the command string
        result = await self.run_shell(command, device_id)
        return {
            "success": result.success,
            "command": command,
            "stdout": result.stdout,
            "stderr": result.stderr,
            "returncode": result.returncode,
        }

    @mcp_tool(
        tags={"developer"},
        annotations={"requires": "developer_mode"},
    )
    async def input_long_press(
        self,
        x: int,
        y: int,
        duration_ms: int = 1000,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Long press at screen coordinates.

        [DEVELOPER MODE] Simulates a long press / press-and-hold gesture.

        Args:
            x: X coordinate
            y: Y coordinate
            duration_ms: Hold duration in milliseconds (default 1000)
            device_id: Target device

        Returns:
            Success status
        """
        if not is_developer_mode():
            return {
                "success": False,
                "error": "Developer mode required",
            }

        # Long press is a swipe with no movement
        result = await self.run_shell_args(
            [
                "input",
                "swipe",
                str(x),
                str(y),
                str(x),
                str(y),
                str(duration_ms),
            ],
            device_id,
        )
        return {
            "success": result.success,
            "action": "long_press",
            "coordinates": {"x": x, "y": y},
            "duration_ms": duration_ms,
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool()
    async def clipboard_set(
        self,
        text: str,
        paste: bool = False,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Set clipboard text and optionally paste.

        Sets the device clipboard to the specified text. Unlike input_text,
        this handles all special characters correctly.

        Use paste=True to immediately paste into the focused field.

        Args:
            text: Text to put on clipboard
            paste: If True, also send Ctrl+V to paste (default False)
            device_id: Target device

        Returns:
            Success status
        """
        # Try cmd clipboard set (Android 12+, injection-safe via args)
        result = await self.run_shell_args(["cmd", "clipboard", "set", text], device_id)

        # Fallback: try am broadcast (Clipper app or similar)
        if not result.success:
            result = await self.run_shell_args(
                [
                    "am",
                    "broadcast",
                    "-a",
                    "clipper.set",
                    "-e",
                    "text",
                    text,
                ],
                device_id,
            )

        preview = text[:100] + "..." if len(text) > 100 else text
        response: dict[str, Any] = {
            "success": result.success,
            "action": "clipboard_set",
            "text": preview,
            "error": result.stderr if not result.success else None,
        }

        # Paste if requested
        if paste and result.success:
            paste_result = await self.run_shell_args(
                ["input", "keyevent", "KEYCODE_PASTE"], device_id
            )
            response["pasted"] = paste_result.success
            if not paste_result.success:
                response["paste_error"] = paste_result.stderr

        return response
