import logging
import shutil
import subprocess
from pathlib import Path

logger = logging.getLogger(__name__)

CLASSIC_TOMTE_CONFIG_DIR = Path("/etc/tomte")
CLASSIC_TOMTE_MODULES_CONFIG = CLASSIC_TOMTE_CONFIG_DIR / "modules.cfg"
CLASSIC_TOMTE_CONFIG_INI = CLASSIC_TOMTE_CONFIG_DIR / "config.ini"
CLASSIC_TOMTE_GRUB_CONFIG = Path("/etc/default/grub")
CLASSIC_TOMTE_GRUB_CMDLINE_KEY = "GRUB_CMDLINE_LINUX_DEFAULT"
CLASSIC_TOMTE_GRUB_CMDLINE_ENTRY = 'GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"'

# Maps classic TUXEDO Tomte fix names as they appear in /etc/tomte/modules.cfg
# to files created by the old fix implementation. These are removed during the
# Tomte -> Tomte Light migration if the fix is listed in the classic config.
CLASSIC_TOMTE_FIX_CREATED_FILES: dict[str, list[Path]] = {
    "amd-gpu-with-nvidia-fix": [
        Path("/usr/share/X11/xorg.conf.d/00-tuxedo-nvidia-amdgpu-fix.conf"),
    ],
    "ath12k-standby-fix": [
        Path("/usr/lib/systemd/system-sleep/tuxedo-ath12k-standby-fix"),
    ],
    "core-fix": [
        Path("/etc/initramfs-tools/hooks/tuxedo-corefix-clevo-nh5xax"),
        Path("/lib/firmware/tuxedo-corefix-clevo-nh5xax/nh5xax.aml"),
    ],
    "early-load-backlight-control-fix": [
        Path("/etc/modprobe.d/tuxedo_early_load_backlight_control_fix.conf"),
    ],
    "gfxmode": [
        Path("/etc/grub.d/gfxmode.cfg"),
    ],
    "lightdm-logind-check-graphical-fix": [
        Path("/etc/lightdm/lightdm.conf.d/99_tuxedo.conf"),
    ],
    "systemd-backlight-fix": [
        Path("/etc/udev/rules.d/99-tuxedo-systemd-backlight-fix.rules"),
    ],
    "tuxedo-restore-audio-fix": [
        Path("/lib/systemd/system/restore-audiocards.service"),
    ],
}

# Maps classic TUXEDO Tomte fix names to GRUB kernel parameters added by the old
# implementation. GRUB is rewritten in one collected pass without regex based
# replacements: the old active line is commented and a clean line is appended.
CLASSIC_TOMTE_FIX_GRUB_MODULES: set[str] = {
    "acpi-backlight-video-fix",
    "amdgpu-backlight-aux-off",
    "amdgpu-disable-psr",
    "amd-xhci-hcd-usb-quirk-suspend-fix",
    "backlight-fix",
    "disable-guc-fix",
    "early-load-backlight-control-fix",
    "elantech-touchpad-fix",
    "i915-enable-psr-fix",
    "no-boot-on-battery",
    "no-ec-wakeup-fix",
    "nvidia-backlight-control-fix",
    "nvme-quirk-switch-to-deep-sleep-fix",
    "text-boot",
    "thunderbolt-boot-delay-fix",
}


def migrate_classic_tomte_fixes() -> bool:
    """
    Roll back classic TUXEDO Tomte fixes which are no longer managed by
    TUXEDO Tomte.

    The classic module config is the source of truth. For each listed fix, files
    created by that fix are removed. GRUB parameters are collected and removed
    in one pass. Afterwards the classic Tomte config is removed.
    """
    if not CLASSIC_TOMTE_MODULES_CONFIG.is_file():
        logger.info("No classic Tomte config found. Nothing to migrate.")
        return True

    fix_names = read_classic_tomte_fix_names()
    if fix_names is None:
        CLASSIC_TOMTE_MODULES_CONFIG.unlink()
        return True

    files_ok = remove_classic_tomte_fix_files(fix_names)
    grub_ok, grub_cmdline_changed = remove_classic_tomte_grub_parameters(fix_names)

    grub_update_needed = grub_cmdline_changed or "gfxmode" in fix_names
    grub_update_ok = run_update_grub() if grub_update_needed else True
    config_ok = remove_classic_tomte_config_files()

    return files_ok and grub_ok and grub_update_ok and config_ok


def read_classic_tomte_fix_names() -> set[str] | None:
    """Read classic Tomte fix names from /etc/tomte/modules.cfg."""
    fix_names: set[str] = set()

    try:
        for raw_line in CLASSIC_TOMTE_MODULES_CONFIG.read_text(
            encoding="utf-8"
        ).splitlines():
            line = raw_line.strip()

            if not line or line.startswith("#"):
                continue

            name = line.split(maxsplit=1)[0]
            if name:
                fix_names.add(name)

    except Exception as e:
        logger.error(
            f"Failed to read classic Tomte config {CLASSIC_TOMTE_MODULES_CONFIG}: {e}"
        )
        return None

    logger.info(f"Found {len(fix_names)} classic Tomte fix entries")
    return fix_names


def remove_classic_tomte_fix_files(fix_names: set[str]) -> bool:
    """Remove files created by classic Tomte fixes listed in the config."""
    success = True

    for fix_name in sorted(fix_names):
        for path in CLASSIC_TOMTE_FIX_CREATED_FILES.get(fix_name, []):
            if not path.exists(follow_symlinks=False):
                logger.debug(f"Legacy Tomte file already absent: {path}")
                continue

            try:
                path.unlink()
                logger.info(f"Removed classic Tomte file for {fix_name}: {path}")
            except Exception as e:
                logger.error(f"Failed to remove classic Tomte file {path}: {e}")
                success = False

    return success


def remove_classic_tomte_grub_parameters(fix_names: set[str]) -> tuple[bool, bool]:
    """
    Remove classic Tomte GRUB changes in one collected, non-regex pass.

    GRUB_CMDLINE_LINUX_DEFAULT is only touched if at least one classic Tomte
    module from the config is known to have changed it.

    Returns:
        tuple[bool, bool]:
            - success
            - grub_cmdline_changed
    """
    grub_fix_names = fix_names & CLASSIC_TOMTE_FIX_GRUB_MODULES

    if not grub_fix_names:
        logger.info("No classic Tomte GRUB cmdline modules found. Nothing to migrate.")
        return True, False

    logger.info(
        f"Found classic Tomte GRUB cmdline modules to migrate: {', '.join(sorted(grub_fix_names))}"
    )

    return rewrite_grub_cmdline()


def rewrite_grub_cmdline() -> tuple[bool, bool]:
    """
    Comment the active GRUB_CMDLINE_LINUX_DEFAULT line and append a clean line.

    Returns:
        tuple[bool, bool]:
            - success
            - changed
    """
    if not CLASSIC_TOMTE_GRUB_CONFIG.exists():
        logger.info(
            f"No GRUB config found at {CLASSIC_TOMTE_GRUB_CONFIG}. Nothing to migrate."
        )
        return True, False

    try:
        lines = CLASSIC_TOMTE_GRUB_CONFIG.read_text(encoding="utf-8").splitlines(
            keepends=True
        )
    except Exception as e:
        logger.error(f"Failed to read {CLASSIC_TOMTE_GRUB_CONFIG}: {e}")
        return False, False

    changed = False
    rewritten_lines: list[str] = []

    for line in lines:
        rewritten_lines.append(line)

        if changed:
            continue

        if not _is_active_grub_cmdline_default(line):
            continue

        if line.strip() == CLASSIC_TOMTE_GRUB_CMDLINE_ENTRY:
            logger.info(
                f"{CLASSIC_TOMTE_GRUB_CONFIG} already contains the default GRUB cmdline."
            )
            return True, False

        rewritten_lines[-1] = f"# {line}" if line.endswith("\n") else f"# {line}\n"

        rewritten_lines.append(
            "# Added by tuxedo-tomte migration after removing classic TUXEDO Tomte kernel parameters line.\n"
        )
        rewritten_lines.append(f"{CLASSIC_TOMTE_GRUB_CMDLINE_ENTRY}\n")
        changed = True

    if not changed:
        logger.info(
            f"No active {CLASSIC_TOMTE_GRUB_CMDLINE_KEY} line found in {CLASSIC_TOMTE_GRUB_CONFIG}"
        )
        return True, False

    try:
        CLASSIC_TOMTE_GRUB_CONFIG.write_text("".join(rewritten_lines), encoding="utf-8")
    except Exception as e:
        logger.error(f"Failed to write {CLASSIC_TOMTE_GRUB_CONFIG}: {e}")
        return False, False

    logger.info(f"Updated {CLASSIC_TOMTE_GRUB_CONFIG}")
    return True, True


def _is_active_grub_cmdline_default(line: str) -> bool:
    stripped = line.lstrip()
    return not stripped.startswith("#") and stripped.startswith(
        f"{CLASSIC_TOMTE_GRUB_CMDLINE_KEY}="
    )


def run_update_grub() -> bool:
    """Run update-grub if available after changing /etc/default/grub."""
    update_grub = shutil.which("update-grub")

    if update_grub is None:
        logger.warning("update-grub not found. GRUB config file was changed only.")
        return True

    try:
        result = subprocess.run(
            [update_grub],
            check=False,
            capture_output=True,
            text=True,
        )
    except Exception as e:
        logger.error(f"Failed to execute update-grub: {e}")
        return False

    if result.stdout:
        logger.info(result.stdout.strip())
    if result.stderr:
        logger.warning(result.stderr.strip())

    if result.returncode != 0:
        logger.error(f"update-grub failed with exit code {result.returncode}")
        return False

    logger.info("update-grub completed successfully")
    return True


def remove_classic_tomte_config_files() -> bool:
    """Remove classic Tomte config files."""
    success = True

    for config_file in (CLASSIC_TOMTE_MODULES_CONFIG, CLASSIC_TOMTE_CONFIG_INI):
        if not config_file.exists(follow_symlinks=False):
            continue

        try:
            config_file.unlink()
            logger.info(f"Removed classic Tomte config file: {config_file}")
        except Exception as e:
            logger.error(
                f"Failed to remove classic Tomte config file {config_file}: {e}"
            )
            success = False

    return success
