"""Tests for author-side branch-identity proofs (Issue #177).

Issue #177 (author-side counterpart of the #173 reviewer proofs) requires
author workflows to *prove* local git state before staging, committing, or
pushing, instead of discovering drift after the fact:

1. The current branch equals the intended feature branch and is never a
   protected branch (master/main/develop/development/dev).
2. Branch or HEAD drift between validation and commit — including external
   branch switches in a shared worktree — stops the workflow.
3. A push requires local branch, remote target branch, and intended issue
   branch to all match.
4. An accidental commit on a protected branch must not be pushed and its
   repair must be reported, never silently continued.

These are the harness assertions from the issue's Required behavior 5.
"""
import sys
import unittest

sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.parent))

from author_proofs import (  # noqa: E402
    PROTECTED_BRANCHES,
    assess_protected_branch_commit,
    build_commit_push_report,
    detect_branch_drift,
    verify_branch_for_commit,
    verify_push_target,
)

FEATURE = "feat/issue-177-branch-drift-proofs"
HEAD_1 = "64dc334a92685b7b6a1fdb7ffe363f02a69f5dbd"
HEAD_2 = "ccc5ef79dfe629853e144763238593bd808d57e0"


class TestProtectedBranches(unittest.TestCase):
    def test_known_protected_names(self):
        for name in ("master", "main", "develop", "development", "dev"):
            self.assertIn(name, PROTECTED_BRANCHES)


class TestVerifyBranchForCommit(unittest.TestCase):
    """Required behavior 1: prove the branch before staging/committing."""

    def test_on_intended_feature_branch_is_proven(self):
        proof = verify_branch_for_commit(FEATURE, FEATURE)
        self.assertTrue(proof["proven"])
        self.assertFalse(proof["block"])

    def test_commit_attempted_while_on_master_is_blocked(self):
        # Harness assertion (behavior 5, bullet 1).
        proof = verify_branch_for_commit("master", FEATURE)
        self.assertFalse(proof["proven"])
        self.assertTrue(proof["block"])
        self.assertTrue(any("master" in r for r in proof["reasons"]))

    def test_every_protected_branch_is_blocked_as_current(self):
        for name in PROTECTED_BRANCHES:
            proof = verify_branch_for_commit(name, FEATURE)
            self.assertTrue(proof["block"], name)

    def test_intended_branch_may_not_be_protected(self):
        proof = verify_branch_for_commit("master", "master")
        self.assertFalse(proof["proven"])
        self.assertTrue(proof["block"])

    def test_wrong_feature_branch_is_blocked(self):
        proof = verify_branch_for_commit("feat/issue-178-other-work", FEATURE)
        self.assertFalse(proof["proven"])
        self.assertTrue(proof["block"])

    def test_missing_current_branch_fails_closed(self):
        proof = verify_branch_for_commit("", FEATURE)
        self.assertTrue(proof["block"])

    def test_missing_intended_branch_fails_closed(self):
        proof = verify_branch_for_commit(FEATURE, None)
        self.assertTrue(proof["block"])


class TestBranchDrift(unittest.TestCase):
    """Required behaviors 2 + 3: drift between validation and commit stops
    the workflow."""

    def test_no_drift_when_branch_and_head_unchanged(self):
        drift = detect_branch_drift(FEATURE, HEAD_1, FEATURE, HEAD_1)
        self.assertFalse(drift["drifted"])
        self.assertFalse(drift["block"])

    def test_branch_drift_between_validation_and_commit_is_blocked(self):
        # Harness assertion (behavior 5, bullet 2).
        drift = detect_branch_drift(FEATURE, HEAD_1, "feat/other", HEAD_1)
        self.assertTrue(drift["drifted"])
        self.assertTrue(drift["block"])

    def test_shared_worktree_branch_switch_is_detected(self):
        # Harness assertion (behavior 5, bullet 4): an external session
        # switching the shared checkout to another branch (e.g. master)
        # must be detected as drift, not treated as exceptional noise.
        drift = detect_branch_drift(FEATURE, HEAD_1, "master", HEAD_1)
        self.assertTrue(drift["drifted"])
        self.assertTrue(drift["block"])
        self.assertTrue(any("switch" in r.lower() for r in drift["reasons"]))

    def test_head_moved_since_validation_is_blocked(self):
        drift = detect_branch_drift(FEATURE, HEAD_1, FEATURE, HEAD_2)
        self.assertTrue(drift["drifted"])
        self.assertTrue(drift["block"])
        self.assertTrue(any("HEAD" in r for r in drift["reasons"]))

    def test_missing_state_fails_closed(self):
        drift = detect_branch_drift(FEATURE, HEAD_1, FEATURE, None)
        self.assertTrue(drift["drifted"])
        self.assertTrue(drift["block"])


class TestVerifyPushTarget(unittest.TestCase):
    """Required behavior 1 (push leg) + acceptance: push needs proof that
    local, remote, and intended branches all match."""

    def test_matching_local_remote_and_intended_is_proven(self):
        proof = verify_push_target(FEATURE, FEATURE, FEATURE)
        self.assertTrue(proof["proven"])
        self.assertFalse(proof["block"])

    def test_push_target_mismatch_is_blocked(self):
        # Harness assertion (behavior 5, bullet 3).
        proof = verify_push_target(FEATURE, "feat/issue-178-other-work", FEATURE)
        self.assertFalse(proof["proven"])
        self.assertTrue(proof["block"])

    def test_local_branch_differs_from_intended_is_blocked(self):
        proof = verify_push_target("feat/other", FEATURE, FEATURE)
        self.assertTrue(proof["block"])

    def test_pushing_a_protected_branch_is_blocked(self):
        proof = verify_push_target("master", "master", "master")
        self.assertFalse(proof["proven"])
        self.assertTrue(proof["block"])

    def test_missing_remote_target_fails_closed(self):
        proof = verify_push_target(FEATURE, "", FEATURE)
        self.assertTrue(proof["block"])


class TestProtectedBranchAccident(unittest.TestCase):
    """Required behavior 4: accidental protected-branch commits must not be
    pushed and their repair must be reported."""

    def test_feature_branch_commit_is_not_an_accident(self):
        result = assess_protected_branch_commit(FEATURE)
        self.assertFalse(result["accident"])
        self.assertEqual(result["violations"], [])

    def test_commit_on_master_is_an_accident_and_must_not_push(self):
        result = assess_protected_branch_commit(
            "master", pushed=False, repair_reported=True
        )
        self.assertTrue(result["accident"])
        self.assertTrue(result["must_not_push"])
        self.assertEqual(result["violations"], [])
        self.assertTrue(result["repair_required"])

    def test_pushing_the_accident_is_a_violation(self):
        result = assess_protected_branch_commit(
            "master", pushed=True, repair_reported=True
        )
        self.assertTrue(any("push" in v.lower() for v in result["violations"]))

    def test_silent_repair_is_a_violation(self):
        # Harness assertion (behavior 5, bullet 5): the repair path must not
        # silently continue without reporting.
        result = assess_protected_branch_commit(
            "master", pushed=False, repair_reported=False
        )
        self.assertTrue(any("report" in v.lower() for v in result["violations"]))


class TestCommitPushReport(unittest.TestCase):
    """Acceptance criteria: the final report includes branch proof before
    commit and before push, and blocks instead of continuing."""

    def _report(self, **overrides):
        kwargs = {
            "commit_proof": verify_branch_for_commit(FEATURE, FEATURE),
            "drift": detect_branch_drift(FEATURE, HEAD_1, FEATURE, HEAD_1),
            "push_proof": verify_push_target(FEATURE, FEATURE, FEATURE),
            "accident": assess_protected_branch_commit(FEATURE),
        }
        kwargs.update(overrides)
        return build_commit_push_report(**kwargs)

    def test_fully_proven_report_is_ok(self):
        report = self._report()
        self.assertEqual(report["status"], "ok")
        self.assertTrue(report["branch_proof_before_commit"])
        self.assertTrue(report["branch_proof_before_push"])
        self.assertFalse(report["drift_detected"])
        self.assertEqual(report["violations"], [])

    def test_commit_proof_failure_blocks(self):
        report = self._report(
            commit_proof=verify_branch_for_commit("master", FEATURE)
        )
        self.assertEqual(report["status"], "blocked")
        self.assertFalse(report["branch_proof_before_commit"])

    def test_drift_blocks(self):
        report = self._report(
            drift=detect_branch_drift(FEATURE, HEAD_1, "master", HEAD_1)
        )
        self.assertEqual(report["status"], "blocked")
        self.assertTrue(report["drift_detected"])

    def test_push_proof_failure_blocks(self):
        report = self._report(
            push_proof=verify_push_target(FEATURE, "feat/other", FEATURE)
        )
        self.assertEqual(report["status"], "blocked")
        self.assertFalse(report["branch_proof_before_push"])

    def test_accident_violations_block(self):
        report = self._report(
            accident=assess_protected_branch_commit(
                "master", pushed=False, repair_reported=False
            )
        )
        self.assertEqual(report["status"], "blocked")
        self.assertTrue(report["violations"])


if __name__ == "__main__":
    unittest.main()
