import os
import platform
import shlex
import subprocess
import time
from pathlib import Path

from .env import OSWorldDesktopEnv
from .execute import execute
from .logging import logger
from .osworld_types import Config
from .util import assert_linux


def execute_configs(env: OSWorldDesktopEnv, configs: list[Config]) -> None:
    """
    Executes a list of configuration actions within the container.

    Parameters:
    env (OSWorldDesktopEnv): The environment with which to execute the configurations.
    configs (list[Config]): A list of configuration objects to be executed.

    Raises:
    RuntimeError: If an unknown configuration type is encountered.

    Configuration types and their actions:
    - "download": Skipped as downloads are handled by the host.
    - "launch": Launches a command with optional shell execution.
    - "execute" or "command": Executes a command and writes output or error to file in cache dir if specified.
    - "open": Opens a specified path.
    - "sleep": Pauses execution for a specified number of seconds.
    - "activate_window": Activates a window by name, with optional strict matching and class-based matching.
    - "close_window": Closes a window by name, with optional strict matching and class-based matching.
    """
    for config in configs:
        if config.type == "download":
            # downloads are done by the host so that the image can run without network access
            pass
        elif config.type == "launch":
            _launch(config.parameters.command, config.parameters.shell)
        elif config.type == "execute" or config.type == "command":
            result = execute(config.parameters.command, config.parameters.shell)
            if result.status == "success":
                if config.parameters.stdout:
                    _write_to_cache_dir(env, config.parameters.stdout, result.output)
                if config.parameters.stderr:
                    _write_to_cache_dir(env, config.parameters.stderr, result.error)
        elif config.type == "open":
            _open(config.parameters.path)
        elif config.type == "sleep":
            _sleep(config.parameters.seconds)
        elif config.type == "activate_window":
            _activate_window(
                config.parameters.window_name,
                config.parameters.strict,
                config.parameters.by_class,
            )
        elif config.type == "close_window":
            _close_window(
                config.parameters.window_name,
                config.parameters.strict,
                config.parameters.by_class,
            )
        elif (
            config.type == "googledrive"
            or config.type == "login"
            or config.type == "chrome_open_tabs"
            or config.type == "chrome_close_tabs"
            or config.type == "update_browse_history"
        ):
            raise NotImplementedError(
                f"Support for config type '{config.type}' not yet implemented"
            )


def _write_to_cache_dir(env: OSWorldDesktopEnv, file_name: str, content: str) -> None:
    with open(os.path.join(env.cache_dir, file_name), "w", encoding="utf-8") as f:
        f.write(content)


def _launch(command: str | list[str], shell: bool | None) -> None:
    logger.info(f"launching {command}")
    shell = shell or False
    if isinstance(command, str):
        command = [command] if shell else shlex.split(command)

    # Expand user directory
    for i, arg in enumerate(command):
        if arg.startswith("~/"):
            command[i] = os.path.expanduser(arg)

    # Adapt to xfce (vs gnome default)
    if command[0] == "nautilus":
        command[0] = "thunar"

    try:
        if "google-chrome" in command and _get_machine_architecture() == "arm":
            index = command.index("google-chrome")
            command[index] = (
                "chromium"  # arm64 chrome is not available yet, can only use chromium
            )
        subprocess.Popen(command, shell=shell)
    except Exception as e:
        print(f"launch {command} failed {e}")
        raise


def _open(path_string: str) -> None:
    path = Path(os.path.expandvars(os.path.expanduser(path_string)))

    if not path.exists():
        raise FileNotFoundError(path)

    assert_linux()
    subprocess.Popen(["xdg-open", str(path)])


def _sleep(seconds: float) -> None:
    logger.info(f"sleeping for {seconds} seconds")
    time.sleep(seconds)


def _close_window(window_name: str, strict: bool | None, by_class: bool | None) -> None:
    strict = strict or False
    by_class = by_class or False
    assert_linux()
    subprocess.run(
        [
            "wmctrl",
            "-{:}{:}c".format("x" if by_class else "", "F" if strict else ""),
            window_name,
        ],
        check=False,
    )


def _activate_window(
    window_name: str, strict: bool | None = False, by_class: bool | None = False
) -> None:
    strict = strict or False
    by_class = by_class or False

    assert_linux()
    # Attempt to activate VS Code window using wmctrl
    subprocess.run(
        [
            "wmctrl",
            "-{:}{:}a".format("x" if by_class else "", "F" if strict else ""),
            window_name,
        ],
        check=False,
    )


def _get_machine_architecture() -> str:
    """Get the machine architecture, e.g., x86_64, arm64, aarch64, i386, etc."""
    architecture = platform.machine().lower()
    if architecture in [
        "amd32",
        "amd64",
        "x86",
        "x86_64",
        "x86-64",
        "x64",
        "i386",
        "i686",
    ]:
        return "amd"
    elif architecture in ["arm64", "aarch64", "aarch32"]:
        return "arm"
    else:
        return "unknown"
