"""Regression: the desktop Electron dependency must be an exact, consistent pin.

The Windows desktop install failed at "Building desktop app" because Electron
changed its install mechanism mid patch-series:

    electron 40.9.3 .. 40.10.2  -> @electron/get@^2 + extract-zip@^2  (pure JS)
    electron 40.10.3 / 40.10.4  -> @electron/get@^5 +
                                   @electron-internal/extract-zip@^1 (native napi)

``apps/desktop/package.json`` declared ``electronVersion: 40.9.3`` (the tested,
JS-extract build) but pinned the dependency loosely as ``electron: ^40.9.3``.
``npm ci`` then resolved 40.10.3/40.10.4 — the new *native* extract-zip whose
win32-x64 binding fails to ``dlopen`` on some Windows hosts
(``ERR_DLOPEN_FAILED loading index.win32-x64-msvc.node``).

These tests lock the contract that prevents that drift, without hard-coding the
specific version (which is allowed to move):

1. the Electron dependency is an *exact* version (Electron Builder needs the
   installed binary to match ``electronVersion`` / ``electronDist``), and
2. the dependency, ``build.electronVersion``, and the resolved lockfile entry
   all agree — so ``npm ci`` installs exactly what the build packages.
"""

from __future__ import annotations

import json
import re
from pathlib import Path

import pytest


REPO_ROOT = Path(__file__).resolve().parent.parent
DESKTOP_PKG = REPO_ROOT / "apps" / "desktop" / "package.json"
ROOT_LOCK = REPO_ROOT / "package-lock.json"

# An exact semver: digits.digits.digits with an optional prerelease/build tag,
# but NO range operators (^ ~ > < = * x || spaces || -range).
_EXACT_SEMVER = re.compile(r"^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$")


def _desktop_pkg() -> dict:
    assert DESKTOP_PKG.is_file(), f"missing {DESKTOP_PKG}"
    return json.loads(DESKTOP_PKG.read_text(encoding="utf-8"))


def _electron_spec(pkg: dict) -> str:
    for section in ("dependencies", "devDependencies"):
        spec = pkg.get(section, {}).get("electron")
        if spec:
            return spec
    pytest.fail("electron is not listed in apps/desktop dependencies")


def test_electron_dependency_is_exactly_pinned():
    """A loose range lets npm drift onto an Electron with a different installer."""
    spec = _electron_spec(_desktop_pkg())
    assert _EXACT_SEMVER.match(spec), (
        f"electron must be pinned to an exact version, got {spec!r}. "
        "A range (^/~) lets npm ci resolve a newer Electron whose postinstall "
        "may differ from the one the build was validated against."
    )


def test_electron_dependency_matches_electron_version():
    """electron-builder packages build.electronVersion against the installed binary."""
    pkg = _desktop_pkg()
    spec = _electron_spec(pkg)
    builder_version = pkg.get("build", {}).get("electronVersion")
    assert builder_version, "build.electronVersion is missing"
    assert spec == builder_version, (
        f"electron dependency ({spec!r}) must equal build.electronVersion "
        f"({builder_version!r}); otherwise electron-builder packages a different "
        "version than npm installs into electronDist."
    )


def test_lockfile_resolves_the_pinned_electron():
    """npm ci installs from the lockfile, so it must agree with the pin."""
    if not ROOT_LOCK.is_file():
        pytest.skip("root package-lock.json not present")
    spec = _electron_spec(_desktop_pkg())
    lock = json.loads(ROOT_LOCK.read_text(encoding="utf-8"))
    packages = lock.get("packages", {})
    resolved = [
        meta.get("version")
        for path, meta in packages.items()
        if path.endswith("node_modules/electron") and meta.get("version")
    ]
    assert resolved, "no electron entry found in package-lock.json"
    assert all(v == spec for v in resolved), (
        f"package-lock.json resolves electron to {sorted(set(resolved))}, "
        f"but the pin is {spec!r}; run `npm install --package-lock-only` so "
        "`npm ci` stays consistent."
    )


DESKTOP_DIR = REPO_ROOT / "apps" / "desktop"
ELECTRON_BUILDER_WRAPPER = DESKTOP_DIR / "scripts" / "run-electron-builder.cjs"


def test_no_static_electron_dist_that_can_drift():
    """build.electronDist must not be a static path — hoisting is non-deterministic."""
    assert "electronDist" not in _desktop_pkg().get("build", {}), (
        "build.electronDist is hardcoded again. npm hoisting is non-deterministic, "
        "so a static path silently breaks packaging when the layout changes. Let "
        "scripts/run-electron-builder.cjs resolve it dynamically instead."
    )


def test_builder_script_routes_through_dynamic_resolver():
    """npm run builder must invoke run-electron-builder.cjs, not bare electron-builder."""
    builder = _desktop_pkg().get("scripts", {}).get("builder", "")
    assert "run-electron-builder.cjs" in builder, (
        f"the 'builder' script must run scripts/run-electron-builder.cjs, got "
        f"{builder!r}"
    )
    assert ELECTRON_BUILDER_WRAPPER.is_file(), (
        f"missing dynamic-resolver wrapper at {ELECTRON_BUILDER_WRAPPER}"
    )


def test_resolver_uses_node_module_resolution():
    """Wrapper must resolve electron via require.resolve and pass -c.electronDist."""
    src = ELECTRON_BUILDER_WRAPPER.read_text(encoding="utf-8")
    assert 'require.resolve("electron/package.json")' in src, (
        "run-electron-builder.cjs must resolve electron via "
        "require.resolve('electron/package.json') to stay hoist-proof."
    )
    # And it must hand the resolved dist to electron-builder as an override.
    assert "-c.electronDist=" in src, (
        "run-electron-builder.cjs must pass the resolved dist to electron-builder "
        "via -c.electronDist."
    )
