"""Unit tests for stacked-PR base policy (#484)."""
import unittest

import stacked_pr_support as sps


def _pr(number, branch, state="open"):
    return {"number": number, "state": state, "head": {"ref": branch}}


OPEN_PRS = [
    _pr(479, "feat/issue-478-mcp-menu-shell"),
    _pr(481, "feat/issue-477-lock-adoption-proof"),
]

# Motivating case (#482 stacked on #479 / #478).
STACKED_BODY = (
    "Closes #482.\n\n"
    "Stacked on PR #479 / issue #478.\n"
    "Base branch: feat/issue-478-mcp-menu-shell\n"
    "Head branch: feat/issue-482-skip-stale-request-changes-pr\n"
    "Do not merge before PR #479 lands."
)


class TestIsBaseBranch(unittest.TestCase):
    def test_master_main_dev_are_base(self):
        for b in ("master", "main", "dev"):
            self.assertTrue(sps.is_base_branch(b))

    def test_feature_branch_is_not_base(self):
        self.assertFalse(sps.is_base_branch("feat/issue-478-mcp-menu-shell"))


class TestStackedBaseDeclaration(unittest.TestCase):
    def test_no_declaration_is_normal_path(self):
        out = sps.assess_stacked_base_declaration(
            stacked_base_branch=None, stacked_base_pr=None, open_prs=OPEN_PRS
        )
        self.assertFalse(out["block"])
        self.assertFalse(out["declared"])
        self.assertIsNone(out["approved"])

    def test_valid_open_pr_base_is_approved(self):
        out = sps.assess_stacked_base_declaration(
            stacked_base_branch="feat/issue-478-mcp-menu-shell",
            stacked_base_pr=479,
            open_prs=OPEN_PRS,
        )
        self.assertFalse(out["block"])
        self.assertEqual(out["approved"]["branch"], "feat/issue-478-mcp-menu-shell")
        self.assertEqual(out["approved"]["pr_number"], 479)
        self.assertTrue(out["approved"]["verified_open"])

    def test_missing_pr_number_blocks(self):
        out = sps.assess_stacked_base_declaration(
            stacked_base_branch="feat/issue-478-mcp-menu-shell",
            stacked_base_pr=None,
            open_prs=OPEN_PRS,
        )
        self.assertTrue(out["block"])
        self.assertIn("without stacked_base_pr", out["reasons"][0])

    def test_arbitrary_branch_with_no_open_pr_blocks(self):
        out = sps.assess_stacked_base_declaration(
            stacked_base_branch="feat/random-unrelated-branch",
            stacked_base_pr=999,
            open_prs=OPEN_PRS,
        )
        self.assertTrue(out["block"])
        self.assertIn("does not correspond to any OPEN pull request", out["reasons"][0])

    def test_stale_merged_base_blocks(self):
        merged = [_pr(479, "feat/issue-478-mcp-menu-shell", state="closed")]
        out = sps.assess_stacked_base_declaration(
            stacked_base_branch="feat/issue-478-mcp-menu-shell",
            stacked_base_pr=479,
            open_prs=merged,
        )
        self.assertTrue(out["block"])
        self.assertIsNone(out["approved"])

    def test_pr_number_mismatch_blocks(self):
        out = sps.assess_stacked_base_declaration(
            stacked_base_branch="feat/issue-478-mcp-menu-shell",
            stacked_base_pr=481,  # wrong PR for this branch
            open_prs=OPEN_PRS,
        )
        self.assertTrue(out["block"])
        self.assertIn("does not match the open", out["reasons"][0])

    def test_declaring_a_base_branch_blocks(self):
        out = sps.assess_stacked_base_declaration(
            stacked_base_branch="master", stacked_base_pr=1, open_prs=OPEN_PRS
        )
        self.assertTrue(out["block"])
        self.assertIn("already a normal base branch", out["reasons"][0])


class TestStackedPrBody(unittest.TestCase):
    def test_complete_body_has_no_missing_fields(self):
        missing = sps.assess_stacked_pr_body(
            STACKED_BODY, base_branch="feat/issue-478-mcp-menu-shell", pr_number=479
        )
        self.assertEqual(missing, [])

    def test_missing_all_fields(self):
        missing = sps.assess_stacked_pr_body(
            "just some text", base_branch="feat/issue-478-mcp-menu-shell", pr_number=479
        )
        self.assertEqual(len(missing), 3)

    def test_missing_merge_ordering_only(self):
        body = "Base branch feat/issue-478-mcp-menu-shell for PR #479"
        missing = sps.assess_stacked_pr_body(
            body, base_branch="feat/issue-478-mcp-menu-shell", pr_number=479
        )
        self.assertEqual(len(missing), 1)
        self.assertIn("merge-ordering", missing[0])


class TestCreatePrBase(unittest.TestCase):
    APPROVED = {"branch": "feat/issue-478-mcp-menu-shell", "pr_number": 479, "verified_open": True}

    def test_master_base_passes_without_stacked_metadata(self):
        out = sps.assess_create_pr_base(
            base="master", approved_stacked_base=None, body="Closes #1", open_prs=[]
        )
        self.assertFalse(out["block"])
        self.assertFalse(out["stacked"])

    def test_non_base_without_approval_blocks(self):
        out = sps.assess_create_pr_base(
            base="feat/issue-478-mcp-menu-shell",
            approved_stacked_base=None,
            body=STACKED_BODY,
            open_prs=OPEN_PRS,
        )
        self.assertTrue(out["block"])
        self.assertIn("no approved stacked base", out["reasons"][0])

    def test_non_base_mismatched_approval_blocks(self):
        out = sps.assess_create_pr_base(
            base="feat/some-other-branch",
            approved_stacked_base=self.APPROVED,
            body=STACKED_BODY,
            open_prs=OPEN_PRS,
        )
        self.assertTrue(out["block"])
        self.assertIn("does not match the issue lock's approved stacked base", out["reasons"][0])

    def test_approved_base_with_good_body_passes(self):
        out = sps.assess_create_pr_base(
            base="feat/issue-478-mcp-menu-shell",
            approved_stacked_base=self.APPROVED,
            body=STACKED_BODY,
            open_prs=OPEN_PRS,
        )
        self.assertFalse(out["block"])
        self.assertTrue(out["stacked"])
        self.assertEqual(out["stacked_base_pr"], 479)

    def test_approved_base_now_stale_blocks(self):
        out = sps.assess_create_pr_base(
            base="feat/issue-478-mcp-menu-shell",
            approved_stacked_base=self.APPROVED,
            body=STACKED_BODY,
            open_prs=[_pr(479, "feat/issue-478-mcp-menu-shell", state="merged")],
        )
        self.assertTrue(out["block"])
        self.assertIn("no longer corresponds to an OPEN", out["reasons"][0])

    def test_approved_base_with_incomplete_body_blocks(self):
        out = sps.assess_create_pr_base(
            base="feat/issue-478-mcp-menu-shell",
            approved_stacked_base=self.APPROVED,
            body="Closes #482 only",
            open_prs=OPEN_PRS,
        )
        self.assertTrue(out["block"])
        self.assertIn("must document the stack", out["reasons"][0])


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