"""Guards for the KDE theme's silent-failure modes.

Every check here corresponds to something that broke without producing an error
message. They are static reads of the scripts and packages because the failures
are configuration-shaped -- there is nothing to import and nothing that raises.
"""
import json
import re
from pathlib import Path

REPO = Path(__file__).resolve().parents[1]
KDE = REPO / "assets" / "themes" / "KDE"
INSTALLER = KDE / "install-plasma.sh"
LNF = KDE / "look-and-feel" / "com.nexusos.desktop"
ICONS = REPO / "assets" / "themes" / "NexusOS-icons"
THEME_INSTALLER = REPO / "assets" / "themes" / "install-theme.sh"


def test_look_and_feel_is_copied_never_symlinked():
    """KPackage skips symlinked package directories without a word, so a
    symlinked Global Theme simply never appears in System Settings."""
    text = INSTALLER.read_text()
    assert "cp -rL" in text, "look-and-feel/wallpaper must be copied into place"
    for line in text.splitlines():
        if line.strip().startswith("ln -s"):
            assert "look-and-feel" not in line and "wallpapers" not in line, \
                f"KPackage package dir must not be symlinked: {line.strip()}"


def test_plasmashell_restart_is_detached_from_the_callers_stdout():
    """The restarted shell outlives the script. Inheriting stdout keeps the
    caller's pipe open forever, which hangs `ncp restore` after a successful
    apply."""
    text = INSTALLER.read_text()
    restart = [l for l in text.splitlines()
               if "kstart5 plasmashell" in l and not l.strip().startswith("#")]
    assert restart, "no plasmashell restart found"
    for line in restart:
        assert "setsid" in line, f"restart not detached: {line.strip()}"
        assert "</dev/null" in line and ">/dev/null" in line, \
            f"restart still holds the caller's stdio: {line.strip()}"


def test_splash_renders_without_the_stage_signal():
    """A splash gated on `stage == 2` shows a blank coloured screen if that
    signal never arrives -- what `ksplashqml --test` does."""
    qml = (LNF / "contents" / "splash" / "Splash.qml").read_text()
    content = qml[qml.index("id: content"):]
    body = content[:content.index("OpacityAnimator")]
    assert "opacity: 0" not in body, "splash content starts invisible"
    assert "introAnimation" not in qml, "visibility still gated on a stage change"


def test_sddm_theme_is_configured_in_exactly_one_place():
    """boot-branding.sh and install-plasma.sh both deploy the SDDM theme; two
    different config files meant the setting could disagree with itself."""
    for script in (INSTALLER, REPO / "bin" / "boot-branding.sh"):
        text = script.read_text()
        stray = re.findall(r"/etc/sddm\.conf(?!\.d)", text)
        assert not stray, f"{script.name} writes bare /etc/sddm.conf; use conf.d"


def test_restore_desktop_stage_covers_plasma_as_well_as_xfce():
    """The desktop stage used to bail out entirely without xfconf-query, so a
    Plasma box got no theme back from `ncp restore` at all."""
    text = (REPO / "bin" / "restore-linux.sh").read_text()
    assert "install-plasma.sh" in text, "restore never invokes the Plasma installer"
    assert "--no-sddm" in text, "restore should leave SDDM to boot-branding.sh"
    # The XFCE check must not be able to skip the Plasma branch or the branding.
    assert "exit 0\nfi\n" not in text.split("desktop stage")[1][:900], \
        "XFCE guard still exits the whole stage"


def test_global_theme_package_is_well_formed():
    meta = json.loads((LNF / "metadata.json").read_text())
    assert meta["KPlugin"]["Id"] == LNF.name, "package Id must match its directory"
    assert "Plasma/LookAndFeel" in meta["KPlugin"]["ServiceTypes"]

    defaults = (LNF / "contents" / "defaults").read_text()
    # Every component the Global Theme selects has to exist in the repo.
    assert "ColorScheme=NexusOS" in defaults
    assert (KDE / "plasma" / "NexusOS").is_dir()
    assert (KDE / "aurorae" / "NexusOS").is_dir()
    assert (KDE / "wallpaper" / "NexusOS" / "metadata.json").is_file()
    assert "Theme=com.nexusos.desktop" in defaults, "splash not wired to this package"


def test_patterned_backgrounds_are_referenced_as_raster_not_svg():
    """QtSvg is SVG Tiny 1.2 and has no <pattern>, so the brushed-metal and
    machine-line textures vanish and the gradient renders flat. Every QML that
    shows that artwork must load the rasterized PNG."""
    qml_files = [
        KDE / "sddm" / "NexusOS-QML" / "Main.qml",
        LNF / "contents" / "splash" / "Splash.qml",
    ]
    for f in qml_files:
        # Only the source: lines -- the comments deliberately mention the SVG,
        # since that is the file you edit and re-rasterize.
        sources = [l for l in f.read_text().splitlines()
                   if "source:" in l and not l.strip().startswith("//")]
        bg = [l for l in sources if "background" in l]
        assert bg, f"{f.name} loads no background"
        for line in bg:
            assert "background.png" in line, \
                f"{f.name} loads a patterned SVG through QtSvg: {line.strip()}"


def _defaults_sections():
    """Parse the look-and-feel defaults into {section: {key: value}}."""
    out, section = {}, None
    for line in (LNF / "contents" / "defaults").read_text().splitlines():
        line = line.strip()
        if line.startswith("["):
            section = line
            out[section] = {}
        elif line and "=" in line and section:
            k, v = line.split("=", 1)
            out[section][k] = v
    return out


def test_lock_screen_theme_names_a_look_and_feel_package():
    """Plasma 5.27 draws the lock screen from a look-and-feel package, and
    [Greeter]Theme names that package. A bare theme name (the old "NexusOS")
    is not one, so Plasma falls back to Breeze without saying anything."""
    greeter = _defaults_sections()["[kscreenlockerrc][Greeter]"]
    assert greeter["Theme"].endswith(".desktop"), \
        f"lock theme must be a look-and-feel package id, got {greeter['Theme']!r}"

    installer = INSTALLER.read_text()
    lock_lines = [l for l in installer.splitlines()
                  if "kscreenlockerrc" in l and "--key Theme" in l]
    assert lock_lines, "installer never sets the lock screen theme"
    for line in lock_lines:
        assert ".desktop" in line, f"installer sets a non-package lock theme: {line.strip()}"

    # And the lock wallpaper is the raster metal background.
    assert "background.png" in installer, "lock wallpaper not set to the metal raster"


def _index_theme():
    """Parse index.theme into (header dict, list of declared directories)."""
    header, section, dirs = {}, None, []
    for line in (ICONS / "index.theme").read_text().splitlines():
        line = line.strip()
        if line.startswith("[") and line != "[Icon Theme]":
            section = line.strip("[]")
            dirs.append(section)
        elif "=" in line and not line.startswith("#") and section is None:
            k, v = line.split("=", 1)
            header[k.strip()] = v.strip()
    return header, dirs


def test_every_declared_icon_directory_exists():
    """index.theme declared 124 directories against 21 real ones, most copied
    from Papirus. A declared-but-missing directory is dead weight the icon
    loader walks on every lookup."""
    _, dirs = _index_theme()
    missing = [d for d in dirs if not (ICONS / d).is_dir()]
    assert not missing, f"index.theme declares directories that do not exist: {missing}"

    listed = set(_index_theme()[0].get("Directories", "").split(","))
    assert listed == set(dirs), "Directories= and the [section] list disagree"


def test_icon_theme_inherits_a_recolourable_parent():
    """Papirus hardcodes its blues, so nothing the colour scheme does can reach
    them and the un-themed surface stayed blue forever. Breeze's icons carry
    ColorScheme-* classes that Plasma recolours from the active scheme."""
    header, _ = _index_theme()
    parents = [p.strip() for p in header["Inherits"].split(",")]
    assert not parents[0].lower().startswith("papirus"), \
        "primary parent cannot recolour from the colour scheme"
    assert parents[0] == "breeze-dark", f"expected breeze-dark first, got {parents[0]!r}"
    assert parents[-1] == "hicolor", "hicolor must remain the last-resort fallback"


def test_icon_theme_is_installed_where_qt_looks():
    """~/.icons is the GTK/XFCE legacy path. Qt/KF5 searches XDG data dirs only,
    so installing there alone meant Plasma never found the theme and every icon
    fell back to Breeze without a word."""
    text = THEME_INSTALLER.read_text()
    links = [l for l in text.splitlines()
             if l.strip().startswith("link ") and "NexusOS-icons" in l]
    assert any(".local/share/icons" in l for l in links), \
        "icon theme is not installed to an XDG data dir; Plasma will not see it"
    assert any(".icons/NexusOS" in l for l in links), \
        "dropping ~/.icons would break the XFCE session and GTK apps"


def test_inherits_check_reads_only_the_primary_parent():
    """The installer compared the whole comma-separated Inherits value against a
    directory name, so a valid multi-parent list warned that an installed
    fallback was missing."""
    text = THEME_INSTALLER.read_text()
    inh = [l for l in text.splitlines() if "INH=" in l and "Inherits" in l]
    assert inh, "inheritance check not found"
    assert any("-f1" in l for l in inh), \
        "inheritance check still treats the whole Inherits list as one theme name"


def test_panel_layout_is_portable():
    """The panel script sets the launcher icon by absolute path. Hardcoding
    this box's home would give any other clone or user a missing icon, so the
    path is a placeholder the installer substitutes."""
    js = (KDE / "panel-layout.js").read_text()
    assert "/home/" not in js, "panel-layout.js hardcodes a home directory"
    assert "__NEXUS_ROOT__" in js, "no placeholder for the repo path"

    installer = INSTALLER.read_text()
    assert "__NEXUS_ROOT__" in installer, "installer never substitutes the repo path"
    # Rewriting the panel wholesale on every restore would wipe later additions.
    assert "PANEL_MARKER" in installer, "panel layout is not guarded by a marker"
