"""Deploy key-level registry merge tests for .agents/hooks.json.

Tests for Rev.2 key-level 3-way merge rules (R-1 to R-10).
"""
import json
import os
import shutil
import subprocess
import tempfile

import pytest

REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))


@pytest.fixture
def ws():
    """A pristine source copy plus empty target directory."""
    tmp = tempfile.mkdtemp(prefix="mam_deploy_reg_")
    src, tgt = os.path.join(tmp, "src"), os.path.join(tmp, "tgt")
    shutil.copytree(REPO_ROOT, src,
                    ignore=shutil.ignore_patterns(".git", ".venv", ".mam",
                                                  ".mam_deploy", "__pycache__"),
                    symlinks=True)
    os.makedirs(tgt)
    try:
        yield src, tgt
    finally:
        shutil.rmtree(tmp, ignore_errors=True)


def _install(src, tgt, **extra):
    env = dict(os.environ)
    env.update({"MAM_REPO_URL": src, "MAM_SKIP_VENV": "1",
                "MAM_SKIP_GITIGNORE": "1"})
    env.update(extra)
    return subprocess.run(["bash", os.path.join(src, "deploy", "install.sh"), tgt],
                          env=env, capture_output=True, text=True)


def _install_mam(src, tgt):
    return subprocess.run(["bash", os.path.join(src, "deploy", "install_mam.sh"),
                          "--target", tgt], capture_output=True, text=True)


def _read_json(path):
    with open(path, encoding="utf-8") as f:
        return json.load(f)


def _write_json(path, data):
    with open(path, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2)


def _add_user_hook(tgt, name="custom-user-hook"):
    p = os.path.join(tgt, ".agents", "hooks.json")
    d = _read_json(p)
    d[name] = {"PreToolUse": [{"matcher": "user_action", "hooks": [{"type": "command", "command": "echo user"}]}]}
    _write_json(p, d)
    return name


def _ship_new_hook(src, name="mam-loop-timeout-guard"):
    script = os.path.join(src, ".agents", "hooks", "loop_timeout_guard.sh")
    with open(script, "w") as f:
        f.write('#!/usr/bin/env bash\necho \'{"decision":"allow"}\'\n')
    os.chmod(script, 0o755)
    p = os.path.join(src, ".agents", "hooks.json")
    d = _read_json(p)
    d[name] = {"PreToolUse": [{"matcher": "file_change", "hooks": [
        {"type": "command", "command": "./hooks/loop_timeout_guard.sh", "timeout": 10}]}]}
    _write_json(p, d)
    return name


def test_r1_user_hook_does_not_block_new_framework_hook(ws):
    src, tgt = ws
    assert _install(src, tgt).returncode == 0

    user_hook = _add_user_hook(tgt)
    new_hook = _ship_new_hook(src)

    res = _install(src, tgt)
    assert res.returncode == 0, res.stderr

    tgt_data = _read_json(os.path.join(tgt, ".agents", "hooks.json"))
    assert user_hook in tgt_data, "User's custom hook was lost"
    assert new_hook in tgt_data, "New framework hook was not delivered"


def test_r2_upstream_fix_to_an_existing_hook_lands(ws):
    src, tgt = ws
    assert _install(src, tgt).returncode == 0

    _add_user_hook(tgt)

    p_src = os.path.join(src, ".agents", "hooks.json")
    d_src = _read_json(p_src)
    key = list(d_src.keys())[0]
    d_src[key]["updated_by_upstream"] = True
    _write_json(p_src, d_src)

    res = _install(src, tgt)
    assert res.returncode == 0, res.stderr

    tgt_data = _read_json(os.path.join(tgt, ".agents", "hooks.json"))
    assert tgt_data.get(key, {}).get("updated_by_upstream") is True


def test_r3_user_modified_hook_is_preserved_and_reported(ws):
    src, tgt = ws
    assert _install(src, tgt).returncode == 0

    p_tgt = os.path.join(tgt, ".agents", "hooks.json")
    d_tgt = _read_json(p_tgt)
    key = list(d_tgt.keys())[0]
    d_tgt[key]["user_custom_setting"] = 123
    _write_json(p_tgt, d_tgt)

    p_src = os.path.join(src, ".agents", "hooks.json")
    d_src = _read_json(p_src)
    d_src[key]["upstream_competing_setting"] = 456
    _write_json(p_src, d_src)

    res = _install(src, tgt)
    assert res.returncode == 0, res.stderr

    tgt_data = _read_json(p_tgt)
    assert tgt_data[key].get("user_custom_setting") == 123, "User edit was overwritten"
    assert "kept yours" in res.stderr or "kept your" in res.stderr or "Registry merged" in res.stderr


def test_r4_retired_upstream_hook_removed_if_unmodified(ws):
    src, tgt = ws
    assert _install(src, tgt).returncode == 0

    p_src = os.path.join(src, ".agents", "hooks.json")
    d_src = _read_json(p_src)
    retired_key = list(d_src.keys())[0]
    del d_src[retired_key]
    _write_json(p_src, d_src)

    res = _install(src, tgt)
    assert res.returncode == 0, res.stderr

    tgt_data = _read_json(os.path.join(tgt, ".agents", "hooks.json"))
    assert retired_key not in tgt_data, "Retired unmodified hook was not removed"


def test_r4b_retired_upstream_hook_kept_if_user_modified(ws):
    src, tgt = ws
    assert _install(src, tgt).returncode == 0

    p_tgt = os.path.join(tgt, ".agents", "hooks.json")
    d_tgt = _read_json(p_tgt)
    retired_key = list(d_tgt.keys())[0]
    d_tgt[retired_key]["modified_by_user"] = True
    _write_json(p_tgt, d_tgt)

    p_src = os.path.join(src, ".agents", "hooks.json")
    d_src = _read_json(p_src)
    del d_src[retired_key]
    _write_json(p_src, d_src)

    res = _install(src, tgt)
    assert res.returncode == 0, res.stderr

    tgt_data = _read_json(p_tgt)
    assert retired_key in tgt_data, "User-modified retired hook should be kept"


def test_r5_merge_preserves_backup_and_stderr_notification(ws):
    src, tgt = ws
    assert _install(src, tgt).returncode == 0

    _add_user_hook(tgt)
    _ship_new_hook(src)

    res = _install(src, tgt)
    assert res.returncode == 0, res.stderr
    assert "Registry merged" in res.stderr or "added" in res.stderr

    backup_dir = os.path.join(tgt, ".mam", "skill-backups")
    assert os.path.exists(backup_dir) and len(os.listdir(backup_dir)) > 0


def test_r6_missing_base_falls_back_to_additive_merge(ws):
    src, tgt = ws
    assert _install(src, tgt).returncode == 0

    shutil.rmtree(os.path.join(tgt, ".mam", "base"), ignore_errors=True)

    user_hook = _add_user_hook(tgt)
    new_hook = _ship_new_hook(src)

    res = _install(src, tgt)
    assert res.returncode == 0, res.stderr

    tgt_data = _read_json(os.path.join(tgt, ".agents", "hooks.json"))
    assert user_hook in tgt_data
    assert new_hook in tgt_data


def test_r7_corrupt_registry_preserved_and_install_continues(ws):
    src, tgt = ws
    assert _install(src, tgt).returncode == 0

    p_tgt = os.path.join(tgt, ".agents", "hooks.json")
    with open(p_tgt, "w") as f:
        f.write("INVALID JSON {{{")

    res = _install(src, tgt)
    assert res.returncode == 0, res.stderr

    with open(p_tgt) as f:
        assert "INVALID JSON" in f.read()


def test_r8_merge_is_idempotent(ws):
    src, tgt = ws
    assert _install(src, tgt).returncode == 0

    _add_user_hook(tgt)
    _ship_new_hook(src)

    res1 = _install(src, tgt)
    assert res1.returncode == 0
    t1_content = open(os.path.join(tgt, ".agents", "hooks.json")).read()

    res2 = _install(src, tgt)
    assert res2.returncode == 0
    t2_content = open(os.path.join(tgt, ".agents", "hooks.json")).read()

    assert t1_content == t2_content


def test_r9_install_mam_records_hash_db_and_base(ws):
    src, tgt = ws
    res = _install_mam(src, tgt)
    assert res.returncode == 0, res.stderr

    assert os.path.exists(os.path.join(tgt, ".mam", "asset_hashes.txt"))
    assert os.path.exists(os.path.join(tgt, ".mam", "base", ".agents", "hooks.json"))


def test_r10_missing_ownership_rules_abort_rather_than_degrade(ws):
    src, tgt = ws
    lib = os.path.join(src, "deploy", "lib_ownership.sh")
    assert os.path.exists(lib), "deploy/lib_ownership.sh is not shipped"
    os.remove(lib)

    res = _install(src, tgt)
    assert res.returncode != 0
    assert "lib_ownership.sh" in res.stderr
