import logging
import os
from threading import Event as TEvent
from threading import Thread
from typing import Sequence

from hhd.plugins import Context, HHDPlugin, HHDSettings, load_relative_yaml
from hhd.plugins.conf import Config
from hhd.plugins.plugin import Emitter

from adjustor.core.acpi import check_perms, initialize
from adjustor.core.const import CPU_DATA, DEV_DATA, ASUS_DATA, MSI_DATA

from .i18n import _

logger = logging.getLogger(__name__)

CONFLICTING_PLUGINS = {
    "SimpleDeckyTDP": "homebrew/plugins/SimpleDeckyTDP",
    "PowerControl": "homebrew/plugins/PowerControl",
}


class AdjustorInitPlugin(HHDPlugin):
    def __init__(
        self,
        min_tdp: int,
        default_tdp: int,
        max_tdp: int,
        use_acpi_call: bool = True,
    ) -> None:
        self.name = f"adjustor_init"
        self.priority = 5
        self.log = "adjs"
        self.init = False
        self.failed = False
        self.has_decky = False
        self.enabled = False
        self.action_enabled = False
        self.use_acpi_call = use_acpi_call

        self.enabled = False
        self.enfoce_limits = True

        self.t = None
        self.should_exit = None

        self.min_tdp = min_tdp
        self.default_tdp = default_tdp
        self.max_tdp = max_tdp
        self.tdp_set = None

    def open(self, emit: Emitter, context: Context):
        self.context = context
        self.emit = emit

    def settings(self):
        v = load_relative_yaml("settings.yml")
        sets = {
            "hhd": {"settings": v["hhd"], "steamos": v["steamos"]},
        }
        if os.environ.get("HHD_ADJ_ENABLE_TDP"):
            sets["hhd"]["settings"]["children"]["tdp_enable"]["default"] = True

        if self.enabled and not self.failed:
            self.action_enabled = False
        else:
            self.action_enabled = True
            sets["tdp"] = {"tdp": v["tdp"]}
            if not self.has_decky:
                del sets["tdp"]["tdp"]["children"]["decky_info"]
                del sets["tdp"]["tdp"]["children"]["decky_remove"]

        return sets

    def update(self, conf: Config):
        if (
            self.action_enabled
            and self.has_decky
            and conf["tdp.tdp.decky_remove"].to(bool)
        ):
            # Preparation
            logger.warning("Removing Decky plugins")
            conf["tdp.tdp.decky_remove"] = False
            conf["hhd.settings.tdp_enable"] = True
            self.has_decky = False
            self.failed = False

            for usr in os.listdir("/home"):
                move_path = os.path.join("/home", usr, "homebrew/plugins/hhd-disabled")
                if os.path.exists(move_path):
                    logger.warning(f"Removing old backup path: '{move_path}'")
                    os.system(f"rm -rf {move_path}")
                os.makedirs(
                    move_path,
                    exist_ok=True,
                )

                logger.warning("Stopping Decky.")
                try:
                    os.system("systemctl stop plugin_loader")
                except Exception as e:
                    logger.error(f"Failed to restart Decky:\n{e}")

                for name, ppath in CONFLICTING_PLUGINS.items():
                    path = os.path.join("/home", usr, ppath)
                    if os.path.exists(path):
                        new_path = os.path.join(move_path, name)
                        logger.warning(
                            f"Moving plugin '{name}' from:\n{path}\nto:\n{new_path}"
                        )
                        os.rename(path, new_path)

                logger.warning("Restarting Decky.")
                try:
                    os.system("systemctl start plugin_loader")
                except Exception as e:
                    logger.error(f"Failed to restart Decky:\n{e}")

            # TDP controls are already enabled.
            logger.warning(f"Enabling TDP controls.")
        
        if self.action_enabled and conf.get_action("tdp.tdp.tdp_enable"):
            conf["hhd.settings.tdp_enable"] = True
            self.failed = False

        enabled = conf.get("hhd.settings.tdp_enable", False)
        
        if not enabled:
            conf["hhd.settings.tdp_ready"] = False
            conf["hhd.steamos.tdp_status"] = "disabled"
            conf["hhd.steamos.tdp_min"] = None
            conf["hhd.steamos.tdp_default"] = None
            conf["hhd.steamos.tdp_max"] = None
            self.init = False

        if self.enabled != enabled:
            self.emit({"type": "settings"})
            if not enabled:
                self.enabled = False
                self._stop()
        self.enabled = enabled

        if not enabled:
            return
        
        new_enforce_limits = conf["hhd.settings.enforce_limits"].to(bool)
        if new_enforce_limits != self.enfoce_limits:
            self.emit({"type": "settings"})
        self.enfoce_limits = new_enforce_limits

        # If steam sets a value other than max TDP, assume TDP slider
        # is enabled. Then, disable it again when max TDP is set but
        # allow max TDP to be set normally to reset it.
        if self.tdp_set is not None:
            conf["hhd.steamos.tdp_set"] = self.tdp_set

        if self.init or not enabled or self.failed:
            return

        for usr in os.listdir("/home"):
            for name, path in CONFLICTING_PLUGINS.items():
                if os.path.exists(os.path.join(usr, path)):
                    err = f'Found "{name}" at:\n{path}\n' + _(
                        "Disable Decky TDP plugins using the button below to continue."
                    )
                    self.emit({"type": "settings"})
                    self.has_decky = True
                    conf["tdp.tdp.tdp_error"] = err
                    conf["hhd.settings.tdp_ready"] = False
                    conf["hhd.steamos.tdp_status"] = "conflict"
                    logger.error(err)
                    self.failed = True
                    self.enabled = False
                    self._stop()
                    return

        if self.use_acpi_call:
            initialize()
            if not check_perms():
                conf["hhd.settings.tdp_ready"] = False
                conf["tdp.tdp.tdp_error"] = (
                    "Can not write to 'acpi_call'. It is required for TDP."
                )
                self.failed = True
                self.enabled = False
                self._stop()
                return
        
        self._start()
        self.failed = False
        self.init = True
        conf["hhd.steamos.tdp_status"] = "enabled"
        conf["hhd.steamos.tdp_min"] = self.min_tdp
        conf["hhd.steamos.tdp_default"] = self.default_tdp
        conf["hhd.steamos.tdp_max"] = self.max_tdp
        conf["hhd.settings.tdp_ready"] = True
        conf["tdp.tdp.tdp_error"] = ""

    def _start(self):
        if self.should_exit:
            return
        self.should_exit = TEvent()
        if not self.t:
            try:
                from .events import loop_process_events

                self.t = Thread(
                    target=loop_process_events, args=(self.emit, self.should_exit)
                )
                self.t.start()
            except Exception as e:
                logger.warning(
                    f"Could not init ACPI event handling. Is pyroute2 installed?"
                )

    def _stop(self):
        if not self.should_exit:
            return
        self.should_exit.set()
        if self.t:
            self.t.join()
            self.t = None
        self.should_exit = None

    def close(self):
        self._stop()
    
    def notify(self, events: Sequence):
        for ev in events:
            if ev["type"] == "tdp":
                self.tdp_set = ev["tdp"] != self.max_tdp

LEGION_GO_DMIS = ["83E1"]
LEGION_GO_2_DMIS = ["83N0", "83N1"]
LEGION_GO_S_DMIS = ["83L3", "83N6", "83Q2", "83Q3"]


def autodetect(existing: Sequence[HHDPlugin]) -> Sequence[HHDPlugin]:
    if os.environ.get("HHD_ADJ_DISABLE"):
        return []

    if len(existing):
        return existing

    from .drivers.asus import AsusDriverPlugin
    from .drivers.lenovo import LenovoDriverPlugin
    from .drivers.msi import MsiDriverPlugin
    from .drivers.smu import SmuDriverPlugin, SmuQamPlugin
    from .drivers.gpu import GpuPlugin
    from .drivers.battery import BatteryPlugin

    drivers = []
    with open("/sys/devices/virtual/dmi/id/product_name") as f:
        prod = f.read().strip()
    with open("/sys/devices/virtual/dmi/id/board_name") as f:
        board = f.read().strip()
    with open("/proc/cpuinfo") as f:
        cpuinfo = f.read().strip()

    use_acpi_call = False
    drivers_matched = False

    # FIXME: Switch to per device
    # But all devices use the same values
    # pretty much
    min_tdp = 4
    default_tdp = 15
    max_tdp = 30

    go_model = None
    if prod in LEGION_GO_S_DMIS:
        go_model = "gos"
        max_tdp = 33
    elif prod in LEGION_GO_DMIS:
        go_model = "go"
        max_tdp = 30
    elif prod in LEGION_GO_2_DMIS:
        go_model = "go2"
        max_tdp = 35

    if go_model and not bool(os.environ.get("HHD_ADJ_ALLY")):
        drivers.append(LenovoDriverPlugin(go_model = go_model))
        drivers_matched = True
        use_acpi_call = True

    for k, v in ASUS_DATA.items():
        if k in prod:
            drivers.append(AsusDriverPlugin(v))
            drivers_matched = True
            min_tdp = v["min_tdp"]
            max_tdp = v["max_tdp"]
            break

    for k, v in MSI_DATA.items():
        if k in board:
            drivers.append(MsiDriverPlugin(v))
            drivers_matched = True
            min_tdp = v["min_tdp"]
            max_tdp = v["max_tdp"]
            break

    if os.environ.get("HHD_ADJ_DEBUG") or os.environ.get("HHD_ENABLE_SMU"):
        drivers_matched = False

    if not drivers_matched and prod in DEV_DATA:
        dev, cpu, pp_enable, energy_map = DEV_DATA[prod]

        try:
            # Set values for the steam slider
            if dev["skin_limit"].smin:
                min_tdp = dev["skin_limit"].smin
            if dev["skin_limit"].default:
                default_tdp = dev["skin_limit"].default
            if dev["skin_limit"].smax:
                max_tdp = dev["skin_limit"].smax
        except Exception as e:
            logger.error(f"Failed to get TDP limits for {prod}:\n{e}")

        pp_enable |= bool(os.environ.get("HHD_ADJ_DEBUG"))
        drivers.append(
            SmuDriverPlugin(
                dev,
                cpu,
                platform_profile=pp_enable,
            )
        )
        drivers.append(
            SmuQamPlugin(
                dev,
                energy_map,
                pp_enable=pp_enable,
                init_tdp=not prod == "83E1",
            ),
        )
        drivers_matched = True
        use_acpi_call = True

    if not drivers_matched:
        for name, (dev, cpu, energy_map) in CPU_DATA.items():
            if name in cpuinfo:
                drivers.append(
                    SmuDriverPlugin(
                        dev,
                        cpu,
                        platform_profile=True,
                    )
                )
                drivers.append(
                    SmuQamPlugin(dev, energy_map),
                )
                use_acpi_call = True
                break

    if not drivers:
        from .drivers.general import GeneralPowerPlugin

        logger.info(f"No tdp drivers found for this device, using generic plugin.")

        return [GeneralPowerPlugin(), BatteryPlugin(always_enable=True)]

    return [
        *drivers,
        AdjustorInitPlugin(
            use_acpi_call=use_acpi_call,
            min_tdp=min_tdp,
            default_tdp=default_tdp,
            max_tdp=max_tdp,
        ),
        BatteryPlugin(),
        GpuPlugin(),
    ]
