"""Regression — task-2553+16 profile-as-code engine.

Validates the test_only_hardening_pr_merge_v1 profile loader/runner binding:
predicate AND-gate, fail-closed auth (9-R.5), deterministic merge_method
(9-R.6), Gemini-thread resolve bound (9-R.1), review-state allowlist (9-R.2),
and idempotency. Pure-unit, no network, no merge side effects.
"""

from __future__ import annotations

import importlib.util
import json
from pathlib import Path

WS = Path("/home/jay/workspace")
SCHEMA = WS / "schemas/policy_profiles/test_only_hardening_pr_merge_v1.schema.json"
PROFILE = WS / "memory/policy_profiles/test_only_hardening_pr_merge_v1.json"
RUNNER = WS / "scripts/run_test_only_hardening_pr_merge.py"


def _load_runner():
    spec = importlib.util.spec_from_file_location("rthpm", RUNNER)
    assert spec is not None and spec.loader is not None
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


R = _load_runner()
PROF = R.load_profile(PROFILE)


def _green_obs():
    return {
        "mergeable": "MERGEABLE",
        "merge_state_status": "CLEAN",
        "review_decision": "",
        "ci_all_success": True,
        "unresolved_review_threads": 0,
        "effective_diff_test_only": True,
        "production_byte0_sha256": "7b7d996aae3c368561f63600f8e71017f7af85b86a63b5533153e956bdec7135",
        "head_sha": "2753e3cf7dad4d0d926d0197875fc3078a3cb19b",
        "repo_allow": {"squash": True, "merge": True, "rebase": True},
    }


# --- schema / profile integrity -------------------------------------------

def test_schema_and_profile_are_valid_json():
    json.loads(SCHEMA.read_text())
    p = json.loads(PROFILE.read_text())
    assert p["profile_id"] == "test_only_hardening_pr_merge_v1"
    assert p["target"]["pr_number"] == 129
    assert p["merge_ready_predicate"]["merge_state_status_in"] == ["CLEAN"]


def test_loader_fail_closed_on_bad_profile(tmp_path):
    bad = tmp_path / "bad.json"
    bad.write_text(json.dumps({"profile_id": "wrong"}))
    try:
        R.load_profile(bad)
        assert False, "expected ValueError"
    except ValueError:
        pass


# --- predicate AND-gate ----------------------------------------------------

def test_predicate_all_pass_when_green():
    res = R.evaluate_predicate(_green_obs(), PROF)
    assert res["ALL_PASS"] is True
    assert res["failed"] == []


def test_predicate_fail_when_blocked():
    obs = _green_obs()
    obs["merge_state_status"] = "BLOCKED"
    res = R.evaluate_predicate(obs, PROF)
    assert res["ALL_PASS"] is False
    assert "merge_state_status_clean" in res["failed"]


def test_predicate_fail_when_unresolved_threads():
    obs = _green_obs()
    obs["unresolved_review_threads"] = 2
    res = R.evaluate_predicate(obs, PROF)
    assert res["ALL_PASS"] is False
    assert "unresolved_review_threads_eq_0" in res["failed"]


def test_predicate_fail_when_head_drift():
    obs = _green_obs()
    obs["head_sha"] = "deadbeef" * 5
    res = R.evaluate_predicate(obs, PROF)
    assert res["ALL_PASS"] is False
    assert "head_sha_eq_sanctioned" in res["failed"]


def test_predicate_fail_when_production_byte_changed():
    obs = _green_obs()
    obs["production_byte0_sha256"] = "0" * 64
    res = R.evaluate_predicate(obs, PROF)
    assert res["ALL_PASS"] is False
    assert "production_byte0" in res["failed"]


# --- 9-R.2 review-state allowlist -----------------------------------------

def test_review_decision_changes_requested_blocks():
    obs = _green_obs()
    obs["review_decision"] = "CHANGES_REQUESTED"
    res = R.evaluate_predicate(obs, PROF)
    assert res["ALL_PASS"] is False
    assert "review_decision_allowlist" in res["failed"]


def test_review_decision_approved_and_empty_allowed():
    for rd in ("APPROVED", "", None):
        obs = _green_obs()
        obs["review_decision"] = rd
        assert R.evaluate_predicate(obs, PROF)["ALL_PASS"] is True


# --- 9-R.5 fail-closed auth ------------------------------------------------

def test_auth_rejects_personal_and_owner_pat():
    assert R.auth_ok("ghp_xxx", PROF)[0] is False
    assert R.auth_ok("github_pat_xxx", PROF)[0] is False
    assert R.auth_ok(None, PROF)[0] is False
    assert R.auth_ok("", PROF)[0] is False


def test_auth_accepts_app_installation_token():
    ok, reason = R.auth_ok("ghs_validapptoken", PROF)
    assert ok is True
    assert reason == "github_app_installation_token"


# --- 9-R.6 deterministic merge_method -------------------------------------

def test_merge_method_squash_first():
    m, prov = R.resolve_merge_method({"squash": True, "merge": True, "rebase": True}, PROF)
    assert m == "squash"
    assert prov["selected"] == "squash"


def test_merge_method_merge_when_no_squash():
    m, _ = R.resolve_merge_method({"squash": False, "merge": True, "rebase": True}, PROF)
    assert m == "merge"


def test_merge_method_rebase_only_holds():
    m, prov = R.resolve_merge_method({"squash": False, "merge": False, "rebase": True}, PROF)
    assert m is None
    assert prov["hold_reason"] == "rebase_only_forbidden"


# --- 9-R.1 Gemini-thread resolve bound ------------------------------------

def test_thread_resolve_requires_exactly_one():
    assert R.thread_resolve_eligible(2, True, False, PROF)[0] is False
    assert R.thread_resolve_eligible(0, True, False, PROF)[0] is False
    assert R.thread_resolve_eligible(1, True, False, PROF)[0] is True


def test_thread_resolve_blocked_when_not_unique_or_other_blockers():
    assert R.thread_resolve_eligible(1, False, False, PROF)[0] is False
    assert R.thread_resolve_eligible(1, True, True, PROF)[0] is False


# --- decide() integration + idempotency -----------------------------------

def test_decide_holds_on_blocked_pr_129_observed():
    obs = _green_obs()
    obs["merge_state_status"] = "BLOCKED"
    obs["unresolved_review_threads"] = 2
    d = R.decide(obs, PROF, "ghs_token", obs["repo_allow"])
    assert d["decision"] == R.PRE_MERGE_HOLD
    assert d["ALL_PASS"] is False


def test_decide_merge_ready_when_green_and_app_token():
    d = R.decide(_green_obs(), PROF, "ghs_token", {"squash": True})
    assert d["decision"] == R.MERGE_READY
    assert d["merge_method_selected"] == "squash"


def test_decide_holds_when_owner_pat_even_if_green():
    d = R.decide(_green_obs(), PROF, "ghp_ownerpat", {"squash": True})
    assert d["decision"] == R.PRE_MERGE_HOLD


def test_decide_is_idempotent():
    obs = _green_obs()
    a = R.decide(obs, PROF, "ghs_token", obs["repo_allow"])
    b = R.decide(obs, PROF, "ghs_token", obs["repo_allow"])
    assert a == b
