import os
import subprocess
from typing import Any, Optional

# the Xlib mode will have been pip installed inside the container. Disable the warnings
# so that it's not annoying when developing this code outside the container.
import Xlib  # type: ignore
from Xlib import X, display  # type: ignore

from .execute import execute
from .logging import logger as base_logger
from .osworld_types import OSWorldProcessResult
from .util import assert_linux

logger = base_logger.getChild("controller")


class OSWorldController:
    def __init__(
        self,
        pkgs_prefix: str = "import pyautogui; import time; pyautogui.FAILSAFE = False; {command}",
    ):
        self.pkgs_prefix = pkgs_prefix  # fixme: this is a hacky way to execute python commands. fix it and combine it with installation of packages
        self.retry_times = 3
        self.retry_interval = 5

    def get_file(self, file_path: str) -> Optional[bytes]:
        logger.info(f"controller getting file '{file_path}'")
        try:
            with open(file_path, "rb") as file:
                return file.read()
        except Exception as e:
            logger.error(f"Failed to get file. {e}")
            return None

    def get_terminal_output(self) -> str | None:
        """Gets the terminal output from the server. None -> no terminal output or unexpected error."""
        raise NotImplementedError("get_terminal_output is not yet implemented")

    def get_vm_screen_size(self) -> dict[str, Any]:
        """Gets the size of the vm screen."""
        assert_linux()
        d = display.Display()
        return {
            "width": d.screen().width_in_pixels,
            "height": d.screen().height_in_pixels,
        }

    def get_vm_window_size(self, app_class_name: str) -> dict[str, Any] | None:
        """Gets the size of the vm app window."""
        d = display.Display()
        root = d.screen().root
        window_ids = root.get_full_property(
            d.intern_atom("_NET_CLIENT_LIST"), X.AnyPropertyType
        ).value

        for window_id in window_ids:
            try:
                window = d.create_resource_object("window", window_id)
                wm_class = window.get_wm_class()

                if wm_class is None:
                    continue

                if app_class_name.lower() in [name.lower() for name in wm_class]:
                    geom = window.get_geometry()
                    return {"width": geom.width, "height": geom.height}
            except Xlib.error.XError:  # Ignore windows that give an error
                continue
        return None

    def get_vm_directory_tree(self, start_path: str) -> dict[str, Any] | None:
        def _list_dir_contents(directory: str) -> dict[str, Any] | dict[str, str]:
            """
            List the contents of a directory recursively, building a tree structure.

            :param directory: The path of the directory to inspect.
            :return: A nested dictionary with the contents of the directory.
            """
            tree: dict[str, Any] = {
                "type": "directory",
                "name": os.path.basename(directory),
                "children": [],
            }
            try:
                # List all files and directories in the current directory
                for entry in os.listdir(directory):
                    full_path = os.path.join(directory, entry)
                    # If entry is a directory, recurse into it
                    if os.path.isdir(full_path):
                        tree["children"].append(_list_dir_contents(full_path))
                    else:
                        tree["children"].append({"type": "file", "name": entry})
            except OSError as e:
                # If the directory cannot be accessed, return the exception message
                tree = {"error": str(e)}
            return tree

        # Ensure the provided path is a directory
        if not os.path.isdir(start_path):
            logger.error(f"The provided path is not a directory {start_path}")
            return None

        return _list_dir_contents(start_path)

    def get_vm_wallpaper(self) -> bytes | None:
        def get_wallpaper_linux() -> str | None:
            try:
                output = subprocess.check_output(
                    ["gsettings", "get", "org.gnome.desktop.background", "picture-uri"],
                    stderr=subprocess.PIPE,
                )
                return (
                    output.decode("utf-8")
                    .strip()
                    .replace("file://", "")
                    .replace("'", "")
                )
            except subprocess.CalledProcessError as e:
                logger.error(f"Call to gsettings failed: {e}")
                return None

        assert_linux()

        wallpaper_path = get_wallpaper_linux()

        if wallpaper_path:
            # return the bytes of the file at wallpaper_path
            try:
                with open(wallpaper_path, "rb") as wallpaper_file:
                    return wallpaper_file.read()
            except Exception as e:
                logger.error(f"Failed to get wallpaper at: {wallpaper_path}", e)
                return None
        return None

    def execute_python_command(self, command: str) -> OSWorldProcessResult:
        return execute(
            ["python3", "-c", self.pkgs_prefix.format(command=command)], None
        )
