import logging
import sys
import time

from pydantic import BaseModel

from test_automation_suite.core.autologin import (
    create_autostart_file,
    enable_autologin,
)
from test_automation_suite.utility.cmd import run_cmd_t
from test_automation_suite.utility.gpu_detection import (
    check_prime_mode,
    check_which_prime_select,
    has_nvidia_gpu,
    has_only_nvidia_gpu,
    is_notebook,
)

logger = logging.getLogger(__name__)


class GraphicsModeRebootEnsurance(BaseModel):
    ensured: bool = True
    reboot_needed: bool = False
    errors: list[str] = []


def _target_mode(desired: str) -> str:
    if desired in {"nvidia", "dgpu"}:
        return "nvidia"
    if desired in {"intel", "igpu"}:
        return "intel"
    return "on-demand"


def _ensure_desktop(desired: str) -> GraphicsModeRebootEnsurance:
    logger.debug("Detected no notebook, assuming desktop pc.")
    if desired in {"on-demand", "dgpu"}:
        logger.debug("Detected 'on-demand' or 'dgpu' as desired mode.")
        return GraphicsModeRebootEnsurance()

    if desired == "nvidia" and has_nvidia_gpu():
        logger.debug("Detected 'nvidia' as desired mode and found a nvidia dgpu.")
        return GraphicsModeRebootEnsurance()

    logger.debug("Detected no valid desired mode for system specs.")
    return GraphicsModeRebootEnsurance(
        ensured=False,
        reboot_needed=False,
        errors=[f"Graphics mode '{desired}' ist not available on this device."],
    )


def _ensure_notebook_no_prime_no_nvidia(desired: str) -> GraphicsModeRebootEnsurance:
    logger.debug("Detected no prime-select and no nvidia.")
    if desired != "nvidia":
        logger.debug("Detected '%s' as desired mode.", desired)
        return GraphicsModeRebootEnsurance()
    logger.debug(
        "Detected 'nvidia' as desired mode, but no prime-select is installed and no nvidia gpu was found.",  # noqa: E501
    )
    return GraphicsModeRebootEnsurance(
        ensured=False,
        reboot_needed=False,
        errors=["Graphics mode 'nvidia' ist not available on this device."],
    )


def _ensure_with_prime_select(target: str) -> GraphicsModeRebootEnsurance:
    current = check_prime_mode()
    logger.debug("Detected prime-mode '%s'.", current)
    logger.debug("target_graphics_mode was set to '%s'.", target)

    if current == target:
        logger.debug("target_graphics_mode is already set to '%s'.", target)
        return GraphicsModeRebootEnsurance()

    # Special case: only-nvidia systems
    if current == "on-demand" and has_only_nvidia_gpu():
        if target == "on-demand":
            logger.debug(
                "Detected 'on-demand' current graphics mode and found only a nvidia dgpu. Keep current mode.",  # noqa: E501
            )
            return GraphicsModeRebootEnsurance()
        if target == "intel":
            err_msg = "Detected 'on-demand' current graphics mode and found only a nvidia dgpu. This is forbidden."  # noqa: E501
            logger.debug(err_msg)
            return GraphicsModeRebootEnsurance(
                ensured=False,
                reboot_needed=False,
                errors=[err_msg],
            )

    _res, err = run_cmd_t(f"prime-select {target}")
    if err:
        return GraphicsModeRebootEnsurance(
            ensured=False,
            reboot_needed=False,
            errors=["prime-select failed with error: " + err],
        )

    # After successful prime-select, a reboot is needed to apply fully.
    return GraphicsModeRebootEnsurance(
        ensured=True,
        reboot_needed=True,
        errors=[],
    )


def _ensure_notebook(desired: str) -> GraphicsModeRebootEnsurance:
    prime_select_installed = check_which_prime_select()
    nvidia_present = has_nvidia_gpu()

    if not prime_select_installed and not nvidia_present:
        return _ensure_notebook_no_prime_no_nvidia(desired)

    if not prime_select_installed:
        return GraphicsModeRebootEnsurance(
            ensured=False,
            reboot_needed=False,
            errors=["Detected no prime-select but a nvidia gpu."],
        )

    target = _target_mode(desired)
    return _ensure_with_prime_select(target)


# Not planned to use with BIOS dGPU mode
def ensure_booted_graphics_mode(
    desired_graphics_mode: str,
) -> GraphicsModeRebootEnsurance:
    valid = {"igpu", "on-demand", "dgpu", "intel", "nvidia"}
    if desired_graphics_mode not in valid:
        err_msg = (
            f"Tried to ensure booted graphics mode with invalid argument '{desired_graphics_mode}'."
        )
        logger.error(err_msg)
        return GraphicsModeRebootEnsurance(
            ensured=False,
            reboot_needed=False,
            errors=[err_msg],
        )

    if not is_notebook():
        return _ensure_desktop(desired_graphics_mode)

    return _ensure_notebook(desired_graphics_mode)


def prepare_reboot():
    enable_autologin()
    autostart_enabled = create_autostart_file()

    if not autostart_enabled:
        logger.fatal("Could not reboot as autostart could not be enabled")
        sys.exit(1)


# called externally from e.g. run_with_args:
# write persistence and ensure reboot gets executed
def reboot() -> None:
    logger.info("reboot requested")

    prepare_reboot()
    run_cmd_t("reboot")

    for _i in range(30):
        time.sleep(1)
        logger.info("Waiting for reboot...")

    # TODO: should we just immediately stop the TAS at this point?
    logger.error("Reboot didn't happen — check permissions")
    time.sleep(120)
    logger.fatal("Reboot didn't happen after waiting additional 120 seconds — check permissions")

    sys.exit(1)
