"""Apps mixin for Android ADB MCP Server.

Provides tools for app management and launching.
"""

import re
from typing import Any

from fastmcp import Context
from fastmcp.contrib.mcp_mixin import mcp_resource, mcp_tool

from ..config import is_developer_mode
from .base import ADBBaseMixin

# Common Android intent flags (hex values for am start -f)
_INTENT_FLAGS: dict[str, int] = {
    "FLAG_ACTIVITY_NEW_TASK": 0x10000000,
    "FLAG_ACTIVITY_CLEAR_TOP": 0x04000000,
    "FLAG_ACTIVITY_SINGLE_TOP": 0x20000000,
    "FLAG_ACTIVITY_NO_HISTORY": 0x40000000,
    "FLAG_ACTIVITY_CLEAR_TASK": 0x00008000,
    "FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS": 0x00800000,
    "FLAG_ACTIVITY_FORWARD_RESULT": 0x02000000,
    "FLAG_ACTIVITY_MULTIPLE_TASK": 0x08000000,
}


class AppsMixin(ADBBaseMixin):
    """Mixin for Android app management.

    Provides tools for:
    - Launching apps
    - Opening URLs
    - Listing packages (developer mode)
    - Installing/uninstalling apps (developer mode)
    """

    @mcp_tool()
    async def app_launch(
        self,
        package_name: str,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Launch an app by package name.

        Starts the main activity of the specified application.

        Common package names:
        - com.android.chrome - Chrome browser
        - com.android.settings - Settings
        - com.android.vending - Play Store
        - com.google.android.gm - Gmail
        - com.google.android.apps.maps - Google Maps

        Args:
            package_name: Android package name (e.g., com.android.chrome)
            device_id: Target device

        Returns:
            Success status
        """
        result = await self.run_shell_args(
            [
                "monkey",
                "-p",
                package_name,
                "-c",
                "android.intent.category.LAUNCHER",
                "1",
            ],
            device_id,
        )
        return {
            "success": result.success,
            "action": "launch",
            "package": package_name,
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool()
    async def app_open_url(
        self,
        url: str,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Open a URL in the default browser.

        Launches the default browser and navigates to the URL.
        Supports http://, https://, and other URL schemes.

        Args:
            url: URL to open (e.g., https://example.com)
            device_id: Target device

        Returns:
            Success status
        """
        result = await self.run_shell_args(
            [
                "am",
                "start",
                "-a",
                "android.intent.action.VIEW",
                "-d",
                url,
            ],
            device_id,
        )
        return {
            "success": result.success,
            "action": "open_url",
            "url": url,
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool()
    async def app_close(
        self,
        package_name: str,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Force stop an app.

        Stops the application and all its background services.

        Args:
            package_name: Package name to stop
            device_id: Target device

        Returns:
            Success status
        """
        result = await self.run_shell_args(
            ["am", "force-stop", package_name], device_id
        )
        return {
            "success": result.success,
            "action": "close",
            "package": package_name,
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool()
    async def app_current(
        self,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Get the currently focused app.

        Returns the package name of the app currently in foreground.

        Args:
            device_id: Target device

        Returns:
            Current app package and activity
        """
        # Use plain "dumpsys window" — the focus fields live at the
        # top level, not inside the "windows" subsection on many devices
        result = await self.run_shell_args(["dumpsys", "window"], device_id)

        if result.success:
            package = None
            activity = None
            for line in result.stdout.split("\n"):
                if "mFocusedApp" in line or "mCurrentFocus" in line:
                    # Match both formats:
                    #   mFocusedApp=ActivityRecord{... com.pkg/.Act t123}
                    #   mCurrentFocus=Window{... com.pkg/com.pkg.Act}
                    match = re.search(r"([\w.]+)/([\w.]*\.?\w+)", line)
                    if match:
                        package = match.group(1)
                        activity = match.group(2)
                        break
            return {
                "success": True,
                "package": package,
                "activity": activity,
                "raw": result.stdout[:500] if not package else None,
            }

        return {
            "success": False,
            "error": result.stderr,
        }

    # === Developer Mode Tools ===

    @mcp_tool(
        tags={"developer"},
        annotations={"requires": "developer_mode"},
    )
    async def app_list_packages(
        self,
        filter_text: str | None = None,
        system_only: bool = False,
        third_party_only: bool = False,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """List installed packages.

        [DEVELOPER MODE] Retrieves all installed application packages.

        Args:
            filter_text: Filter packages containing this text
            system_only: Only show system packages
            third_party_only: Only show third-party (user installed) packages
            device_id: Target device

        Returns:
            List of package names
        """
        if not is_developer_mode():
            return {
                "success": False,
                "error": "Developer mode required",
            }

        cmd = ["pm", "list", "packages"]
        if system_only:
            cmd.append("-s")
        elif third_party_only:
            cmd.append("-3")

        result = await self.run_shell_args(cmd, device_id)

        if result.success:
            packages = []
            for line in result.stdout.split("\n"):
                if line.startswith("package:"):
                    pkg = line.replace("package:", "").strip()
                    if filter_text is None or filter_text.lower() in pkg.lower():
                        packages.append(pkg)

            return {
                "success": True,
                "packages": sorted(packages),
                "count": len(packages),
            }

        return {
            "success": False,
            "error": result.stderr,
        }

    @mcp_tool(
        tags={"developer"},
        annotations={"requires": "developer_mode"},
    )
    async def app_install(
        self,
        apk_path: str,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Install an APK file.

        [DEVELOPER MODE] Installs an APK from the host machine to the device.

        Args:
            apk_path: Path to APK file on host machine
            device_id: Target device

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

        result = await self.run_adb(["install", "-r", apk_path], device_id)
        return {
            "success": result.success,
            "action": "install",
            "apk": apk_path,
            "output": result.stdout,
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool(
        tags={"developer"},
        annotations={"requires": "developer_mode"},
    )
    async def app_uninstall(
        self,
        ctx: Context,
        package_name: str,
        keep_data: bool = False,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Uninstall an app.

        [DEVELOPER MODE] Removes an application from the device.
        Requires user confirmation before proceeding.

        Args:
            ctx: MCP context for elicitation/logging
            package_name: Package to uninstall
            keep_data: Keep app data after uninstall
            device_id: Target device

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

        # Elicit confirmation
        await ctx.warning(f"Uninstall requested: {package_name}")

        data_note = " (keeping app data)" if keep_data else " (all data will be lost)"
        confirmation = await ctx.elicit(
            f"Are you sure you want to uninstall '{package_name}'?{data_note}",
            ["Yes, uninstall", "Cancel"],
        )

        if confirmation.action != "accept" or confirmation.content == "Cancel":
            await ctx.info("Uninstall cancelled by user")
            return {
                "success": False,
                "cancelled": True,
                "message": "Uninstall cancelled by user",
            }

        await ctx.info(f"Uninstalling {package_name}...")

        cmd = ["uninstall"]
        if keep_data:
            cmd.append("-k")
        cmd.append(package_name)

        result = await self.run_adb(cmd, device_id)

        if result.success:
            await ctx.info(f"Successfully uninstalled {package_name}")
        else:
            await ctx.error(f"Uninstall failed: {result.stderr}")

        return {
            "success": result.success,
            "action": "uninstall",
            "package": package_name,
            "kept_data": keep_data,
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool(
        tags={"developer"},
        annotations={"requires": "developer_mode"},
    )
    async def app_clear_data(
        self,
        ctx: Context,
        package_name: str,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Clear app data and cache.

        [DEVELOPER MODE] Clears all data for an application (like a fresh
        install). Requires user confirmation before proceeding.

        Args:
            ctx: MCP context for elicitation/logging
            package_name: Package to clear
            device_id: Target device

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

        # Elicit confirmation
        await ctx.warning(f"Clear data requested: {package_name}")

        confirmation = await ctx.elicit(
            f"Are you sure you want to clear ALL data for "
            f"'{package_name}'? This includes login state, settings, "
            "saved files, and cache. The app will be reset to a "
            "fresh install state.",
            ["Yes, clear all data", "Cancel"],
        )

        if confirmation.action != "accept" or confirmation.content == "Cancel":
            await ctx.info("Clear data cancelled by user")
            return {
                "success": False,
                "cancelled": True,
                "message": "Clear data cancelled by user",
            }

        await ctx.info(f"Clearing data for {package_name}...")

        result = await self.run_shell_args(["pm", "clear", package_name], device_id)

        if result.success:
            await ctx.info(f"Successfully cleared data for {package_name}")
        else:
            await ctx.error(f"Clear data failed: {result.stderr}")

        return {
            "success": result.success,
            "action": "clear_data",
            "package": package_name,
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool(
        tags={"developer"},
        annotations={"requires": "developer_mode"},
    )
    async def activity_start(
        self,
        component: str,
        action: str | None = None,
        data_uri: str | None = None,
        extras: dict[str, str] | None = None,
        flags: list[str] | None = None,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Start a specific activity with intent.

        [DEVELOPER MODE] Launch an activity with full intent control.
        More powerful than app_launch for deep linking and testing.

        Args:
            component: Activity component (e.g., "com.example/.MainActivity")
            action: Intent action (e.g., "android.intent.action.VIEW")
            data_uri: Data URI to pass to activity
            extras: Extra key-value pairs to include in intent
            flags: Intent flag names (e.g., ["FLAG_ACTIVITY_NEW_TASK"])
                   or hex values (e.g., ["0x10000000"])
            device_id: Target device

        Returns:
            Activity start result

        Examples:
            Start specific activity:
                component="com.example/.MainActivity"
            Deep link with data:
                component="com.example/.DeepLinkActivity"
                data_uri="myapp://product/123"
        """
        if not is_developer_mode():
            return {
                "success": False,
                "error": "Developer mode required",
            }

        cmd_args = ["am", "start"]

        if action:
            cmd_args.extend(["-a", action])

        if data_uri:
            cmd_args.extend(["-d", data_uri])

        # Resolve flag names to integer values for am start -f
        if flags:
            combined_flags = 0
            for flag in flags:
                flag_clean = flag.strip()
                if flag_clean.startswith("0x"):
                    combined_flags |= int(flag_clean, 16)
                elif flag_clean in _INTENT_FLAGS:
                    combined_flags |= _INTENT_FLAGS[flag_clean]
                elif flag_clean.isdigit():
                    combined_flags |= int(flag_clean)
            if combined_flags:
                cmd_args.extend(["-f", str(combined_flags)])

        if extras:
            for key, value in extras.items():
                if value.lower() in ("true", "false"):
                    cmd_args.extend(["--ez", key, value.lower()])
                elif re.match(r"^-?\d+$", value):
                    cmd_args.extend(["--ei", key, value])
                else:
                    cmd_args.extend(["--es", key, value])

        cmd_args.extend(["-n", component])

        result = await self.run_shell_args(cmd_args, device_id)

        return {
            "success": result.success,
            "action": "activity_start",
            "component": component,
            "intent_action": action,
            "data_uri": data_uri,
            "output": result.stdout,
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool(
        tags={"developer"},
        annotations={"requires": "developer_mode"},
    )
    async def broadcast_send(
        self,
        action: str,
        extras: dict[str, str] | None = None,
        package: str | None = None,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Send a broadcast intent.

        [DEVELOPER MODE] Sends a broadcast that can be received by
        BroadcastReceivers. Useful for testing and triggering app behavior.

        Args:
            action: Broadcast action (e.g., "com.example.MY_ACTION")
            extras: Extra key-value pairs to include
            package: Limit to specific package (optional)
            device_id: Target device

        Returns:
            Broadcast result

        Common system broadcasts (for testing receivers):
            - android.intent.action.AIRPLANE_MODE
            - android.intent.action.BATTERY_LOW
            - android.net.conn.CONNECTIVITY_CHANGE
        """
        if not is_developer_mode():
            return {
                "success": False,
                "error": "Developer mode required",
            }

        cmd_args = ["am", "broadcast", "-a", action]

        if package:
            cmd_args.extend(["-p", package])

        if extras:
            for key, value in extras.items():
                if value.lower() in ("true", "false"):
                    cmd_args.extend(["--ez", key, value.lower()])
                elif re.match(r"^-?\d+$", value):
                    cmd_args.extend(["--ei", key, value])
                else:
                    cmd_args.extend(["--es", key, value])

        result = await self.run_shell_args(cmd_args, device_id)

        return {
            "success": result.success,
            "action": "broadcast_send",
            "broadcast_action": action,
            "package": package,
            "output": result.stdout,
            "error": result.stderr if not result.success else None,
        }

    # === Resources ===

    @mcp_resource(uri="adb://apps/current")
    async def resource_current_app(self) -> dict[str, Any]:
        """Resource: Get currently focused app."""
        return await self.app_current()
