"""Files mixin for Android ADB MCP Server.

Provides tools for file transfer between host and device.
"""

from pathlib import Path
from typing import Any

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

from ..config import is_developer_mode
from .base import ADBBaseMixin


class FilesMixin(ADBBaseMixin):
    """Mixin for Android file operations.

    Provides tools for:
    - Pushing files to device (developer mode)
    - Pulling files from device (developer mode)
    - Listing files on device (developer mode)
    """

    @mcp_tool(
        tags={"developer"},
        annotations={"requires": "developer_mode"},
    )
    async def file_push(
        self,
        ctx: Context,
        local_path: str,
        device_path: str,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Push a file from host to device.

        [DEVELOPER MODE] Transfers a file from the local machine to the
        Android device. Useful for deploying configs, test data, APKs, etc.

        Common destinations:
        - /sdcard/ - External storage (accessible without root)
        - /sdcard/Download/ - Downloads folder
        - /data/local/tmp/ - Temp directory for executables

        Args:
            ctx: MCP context for logging
            local_path: Path to file on host machine
            device_path: Destination path on device (e.g., /sdcard/file.txt)
            device_id: Target device

        Returns:
            Transfer result with bytes transferred
        """
        if not is_developer_mode():
            return {
                "success": False,
                "error": "Developer mode required",
            }

        # Verify local file exists
        local = Path(local_path)
        if not local.exists():
            return {
                "success": False,
                "error": f"Local file not found: {local_path}",
            }

        file_size = local.stat().st_size
        await ctx.info(f"Pushing {local.name} ({file_size:,} bytes) to {device_path}")

        result = await self.run_adb(
            ["push", str(local.absolute()), device_path], device_id
        )

        if result.success:
            await ctx.info(f"Successfully pushed {local.name}")
        else:
            await ctx.error(f"Push failed: {result.stderr}")

        return {
            "success": result.success,
            "action": "push",
            "local_path": str(local.absolute()),
            "device_path": device_path,
            "output": result.stdout,
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool(
        tags={"developer"},
        annotations={"requires": "developer_mode"},
    )
    async def file_pull(
        self,
        ctx: Context,
        device_path: str,
        local_path: str | None = None,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Pull a file from device to host.

        [DEVELOPER MODE] Transfers a file from the Android device to the
        local machine. Useful for retrieving logs, databases, screenshots.

        Common sources:
        - /sdcard/ - External storage
        - /data/data/<package>/databases/ - App databases (may need root)
        - /sdcard/Android/data/<package>/ - App external data

        Args:
            ctx: MCP context for logging
            device_path: Path to file on device
            local_path: Destination on host (default: current dir with same name)
            device_id: Target device

        Returns:
            Transfer result with local file path
        """
        if not is_developer_mode():
            return {
                "success": False,
                "error": "Developer mode required",
            }

        # Default local path to current directory with same filename
        if not local_path:
            local_path = Path(device_path).name

        local = Path(local_path).absolute()
        local.parent.mkdir(parents=True, exist_ok=True)

        await ctx.info(f"Pulling {device_path} to {local}")

        result = await self.run_adb(["pull", device_path, str(local)], device_id)

        if result.success:
            await ctx.info(f"Successfully pulled to {local}")
        else:
            await ctx.error(f"Pull failed: {result.stderr}")

        return {
            "success": result.success,
            "action": "pull",
            "device_path": device_path,
            "local_path": str(local),
            "output": result.stdout,
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool(
        tags={"developer"},
        annotations={"requires": "developer_mode"},
    )
    async def file_list(
        self,
        device_path: str = "/sdcard/",
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """List files in a directory on the device.

        [DEVELOPER MODE] Lists files and directories at the specified path.

        Args:
            device_path: Directory path on device (default: /sdcard/)
            device_id: Target device

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

        result = await self.run_shell_args(["ls", "-la", device_path], device_id)

        if not result.success:
            return {
                "success": False,
                "error": result.stderr or "Failed to list directory",
            }

        # Parse ls output — Android uses ISO dates (YYYY-MM-DD HH:MM)
        # while traditional ls uses (Mon DD HH:MM), so date takes 2 or 3 fields
        files = []
        for line in result.stdout.strip().split("\n"):
            if not line or line.startswith("total"):
                continue

            parts = line.split()
            if len(parts) < 7:
                continue

            # Detect date format: ISO "2024-01-15" vs traditional "Jan 15"
            # ISO dates have a dash at index 4 of the date field
            date_field = parts[5]
            if len(date_field) == 10 and date_field[4:5] == "-":
                # Android ISO format: perms links owner group size YYYY-MM-DD HH:MM name
                date_str = f"{parts[5]} {parts[6]}"
                name = " ".join(parts[7:])
            elif len(parts) >= 8:
                # Traditional format: perms links owner group size Mon DD HH:MM name
                date_str = f"{parts[5]} {parts[6]} {parts[7]}"
                name = " ".join(parts[8:])
            else:
                continue

            files.append(
                {
                    "permissions": parts[0],
                    "size": parts[4],
                    "date": date_str,
                    "name": name,
                    "is_directory": parts[0].startswith("d"),
                }
            )

        return {
            "success": True,
            "path": device_path,
            "files": files,
            "count": len(files),
        }

    @mcp_tool(
        tags={"developer"},
        annotations={"requires": "developer_mode"},
    )
    async def file_delete(
        self,
        ctx: Context,
        device_path: str,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Delete a file on the device.

        [DEVELOPER MODE] Removes a file from the device storage.
        Requires user confirmation. Deletion is permanent.

        Args:
            ctx: MCP context for elicitation/logging
            device_path: Path to file on device
            device_id: Target device

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

        # Elicit confirmation
        await ctx.warning(f"Delete requested: {device_path}")

        confirmation = await ctx.elicit(
            f"Are you sure you want to delete '{device_path}'? This cannot be undone.",
            ["Yes, delete", "Cancel"],
        )

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

        await ctx.info(f"Deleting {device_path}...")

        result = await self.run_shell_args(["rm", device_path], device_id)

        if result.success:
            await ctx.info(f"Successfully deleted {device_path}")
        else:
            await ctx.error(f"Delete failed: {result.stderr}")

        return {
            "success": result.success,
            "action": "delete",
            "path": device_path,
            "error": result.stderr if not result.success else None,
        }

    @mcp_tool(
        tags={"developer"},
        annotations={"requires": "developer_mode"},
    )
    async def file_exists(
        self,
        device_path: str,
        device_id: str | None = None,
    ) -> dict[str, Any]:
        """Check if a file exists on the device.

        [DEVELOPER MODE] Tests for file existence.

        Args:
            device_path: Path to check on device
            device_id: Target device

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

        # Use test -e and check returncode (injection-safe via run_shell_args)
        result = await self.run_shell_args(["test", "-e", device_path], device_id)

        return {
            "success": True,
            "path": device_path,
            "exists": result.success,
        }
