# tests/test_prevent_install.py
"""install.py — the no-clobber hook chainer. Focus the safety-critical invariants: it NEVER overwrites the
existing hook, the chain block preserves the prior command's exit (no-swallow) and fails open on an absent
path, idempotent re-run REPLACES in place (so report-only->enforce updates), husky redirects to the authored
committed file (not the inert .git/hooks), and --uninstall restores the original. Pure filesystem → pass/fail."""
import importlib.util, os, subprocess

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


def _load(name, path):
    spec = importlib.util.spec_from_file_location(name, path)
    mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod); return mod


install = _load("sg_install", os.path.join(ROOT, "prevent", "install.py"))


def _git(repo, *args):
    return subprocess.run(["git", "-C", str(repo), *args], capture_output=True, text=True)


def _init(tmp_path):
    repo = tmp_path / "repo"; repo.mkdir()
    _git(repo, "init")
    return repo


def test_append_preserves_existing_hook_and_chains_block(tmp_path):
    repo = _init(tmp_path)
    hook = repo / ".git" / "hooks" / "pre-commit"
    hook.parent.mkdir(parents=True, exist_ok=True)
    hook.write_text("#!/usr/bin/env sh\nnpx lint-staged\n", encoding="utf-8")
    target, mech, action = install.apply(str(repo), report_only=True)
    assert os.path.realpath(target) == os.path.realpath(str(hook)) and mech == "git" and action == "installed"
    txt = hook.read_text(encoding="utf-8")
    assert "npx lint-staged" in txt, "must NOT clobber the existing hook"
    assert install.BEGIN in txt and install.END in txt
    assert "--report-only" in txt
    assert '_pb_rc=$?; [ "$_pb_rc" -ne 0 ] && exit "$_pb_rc"' in txt, "no-swallow: must preserve prior exit"
    assert f'if [ -f "{install.PREVENT_PY}" ]; then' in txt, "fail-open: must guard the absent-path case"
    assert os.stat(hook).st_mode & 0o100, "hook must be executable"


def test_idempotent_replace_in_place_not_duplicate(tmp_path):
    repo = _init(tmp_path)
    install.apply(str(repo), report_only=True)
    _, _, action2 = install.apply(str(repo), report_only=True)
    txt = (repo / ".git" / "hooks" / "pre-commit").read_text(encoding="utf-8")
    assert txt.count(install.BEGIN) == 1 and txt.count(install.END) == 1, "re-run must REPLACE, not duplicate"
    assert action2 == "updated"


def test_flip_report_only_to_enforce_updates_invocation(tmp_path):
    repo = _init(tmp_path)
    install.apply(str(repo), report_only=True)
    install.apply(str(repo), report_only=False)  # flip to enforce by re-install
    txt = (repo / ".git" / "hooks" / "pre-commit").read_text(encoding="utf-8")
    assert txt.count(install.BEGIN) == 1
    assert "--report-only" not in txt, "enforce re-install must drop --report-only"


def test_uninstall_restores_original(tmp_path):
    repo = _init(tmp_path)
    hook = repo / ".git" / "hooks" / "pre-commit"
    hook.parent.mkdir(parents=True, exist_ok=True)
    original = "#!/usr/bin/env bash\nnode some-existing-gate || exit 1\n"
    hook.write_text(original, encoding="utf-8")
    install.apply(str(repo), report_only=True)
    install.apply(str(repo), uninstall=True)
    assert hook.read_text(encoding="utf-8") == original, "uninstall must leave the original hook byte-identical"


def test_fresh_hook_created_with_shebang(tmp_path):
    repo = _init(tmp_path)
    target, _, action = install.apply(str(repo), report_only=True)
    txt = open(target, encoding="utf-8").read()
    assert txt.startswith("#!"), "a fresh hook must get a shebang"
    assert install.BEGIN in txt and action == "installed"
    assert os.stat(target).st_mode & 0o100


def test_husky_targets_authored_committed_file_not_inert_git_hooks(tmp_path):
    repo = _init(tmp_path)
    (repo / ".husky" / "_").mkdir(parents=True)
    (repo / ".husky" / "pre-commit").write_text("#!/usr/bin/env sh\nnpx lint-staged\n", encoding="utf-8")
    _git(repo, "config", "core.hooksPath", ".husky/_")
    target, mech = install.hook_target(str(repo))
    assert mech == "husky"
    assert os.path.realpath(target) == os.path.realpath(str(repo / ".husky" / "pre-commit")), \
        "husky: must target the authored file, NOT the inert .git/hooks"


def test_default_target_is_git_hooks(tmp_path):
    repo = _init(tmp_path)
    target, mech = install.hook_target(str(repo))
    assert mech == "git" and target.endswith(os.path.join(".git", "hooks", "pre-commit"))


def test_is_tracked_true_for_committed_false_for_gitignored(tmp_path):
    # the husky 'commit it' hint must be honest: a gitignored .husky/pre-commit is LOCAL, NOT to be committed.
    repo = _init(tmp_path)
    _git(repo, "config", "user.email", "t@t"); _git(repo, "config", "user.name", "t")
    (repo / ".husky").mkdir()
    tracked = repo / ".husky" / "pre-commit"
    tracked.write_text("#!/usr/bin/env sh\n", encoding="utf-8")
    _git(repo, "add", ".husky/pre-commit"); _git(repo, "commit", "-m", "add husky hook")
    assert install.is_tracked(str(repo), str(tracked)) is True

    # now a repo that gitignores .husky/ — same path, but untracked → hint must NOT say 'commit it'
    (tmp_path / "two").mkdir()
    repo2 = _init(tmp_path / "two")
    (repo2 / ".gitignore").write_text(".husky/\n", encoding="utf-8")
    (repo2 / ".husky").mkdir()
    local = repo2 / ".husky" / "pre-commit"
    local.write_text("#!/usr/bin/env sh\n", encoding="utf-8")
    assert install.is_tracked(str(repo2), str(local)) is False
