"""Integration tests for reconciler merged cleanup (#523)."""
import os
import sys
import unittest
from unittest.mock import patch

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

import mcp_server
import task_capability_map
from audit_reconciliation_mode import clear_phase
from task_capability_map import required_role

RECONCILER_PROFILE = {
    "profile_name": "prgs-reconciler",
    "context": "prgs",
    "role": "reconciler",
    "username": "sysadmin",
    "base_url": "https://gitea.prgs.cc",
    "allowed_operations": [
        "gitea.read",
        "gitea.pr.close",
        "gitea.pr.comment",
        "gitea.issue.comment",
    ],
    "forbidden_operations": [],
}

AUTHOR_PROFILE = {
    "profile_name": "prgs-author",
    "context": "prgs",
    "role": "author",
    "username": "jcwalker3",
    "base_url": "https://gitea.prgs.cc",
    "allowed_operations": [
        "gitea.read",
        "gitea.pr.create",
        "gitea.branch.create",
        "gitea.branch.push",
        "gitea.repo.commit",
    ],
    "forbidden_operations": [],
}

CONTROL_ROOT = "/Users/jasonwalker/Development/Gitea-Tools"


class TestReconcilerCleanupIntegration(unittest.TestCase):
    def setUp(self):
        clear_phase()
        mcp_server._preflight_whoami_called = False
        mcp_server._preflight_capability_called = False
        mcp_server._preflight_resolved_role = None
        mcp_server._preflight_resolved_task = None
        mcp_server._preflight_whoami_violation = False
        mcp_server._preflight_capability_violation = False
        mcp_server._preflight_whoami_violation_files = []
        mcp_server._preflight_capability_violation_files = []
        mcp_server._preflight_capability_baseline_porcelain = ""
        self.mock_api = patch("gitea_auth.api_request").start()
        self.mock_auth = patch(
            "mcp_server.get_auth_header", return_value="token test"
        ).start()

    def tearDown(self):
        patch.stopall()
        clear_phase()
        mcp_server._IDENTITY_CACHE.clear()

    def test_capability_map_routes_merged_cleanup_to_reconciler(self):
        self.assertEqual(required_role("reconcile_merged_cleanups"), "reconciler")
        self.assertEqual(required_role("reconciliation_cleanup"), "reconciler")
        self.assertEqual(
            task_capability_map.required_permission("reconcile_merged_cleanups"),
            "gitea.read",
        )

    @patch.dict(os.environ, {"GITEA_PROFILE_NAME": "prgs-reconciler"}, clear=True)
    @patch("mcp_server.get_profile", return_value=RECONCILER_PROFILE)
    def test_reconciler_can_run_reconcile_merged_cleanups_directly(self, _profile):
        mcp_server.record_preflight_check("whoami")
        mcp_server.record_preflight_check(
            "capability",
            resolved_role="reconciler",
            resolved_task="reconcile_merged_cleanups",
        )

        self.mock_api.side_effect = [
            [  # closed pulls page
                {
                    "number": 100,
                    "title": "merged feature",
                    "body": "Closes #100",
                    "merged": True,
                    "merged_at": "2026-07-06T12:00:00Z",
                    "merge_commit_sha": "deadbeef",
                    "head": {"ref": "feat/issue-100", "sha": "cafebabe"},
                },
                {
                    # closed but NOT merged — must not become a cleanup entry
                    "number": 101,
                    "title": "abandoned",
                    "body": "Closes #101",
                    "merged": False,
                    "merged_at": None,
                    "head": {"ref": "feat/issue-101", "sha": "badcafe"},
                },
            ],
            [],  # open pulls
        ]

        with patch("mcp_server._remote_branch_exists", return_value=True):
            with patch(
                "mcp_server.merged_cleanup_reconcile.is_head_ancestor_of_ref",
                return_value=True,
            ):
                result = mcp_server.gitea_reconcile_merged_cleanups(
                    dry_run=True,
                    remote="prgs",
                )
        self.assertTrue(result["success"])
        self.assertTrue(result["dry_run"])
        entry_numbers = [e["issue_number"] for e in result["entries"]]
        self.assertEqual(entry_numbers, [100])
        self.assertNotIn(101, entry_numbers)

    @patch.dict(
        os.environ,
        {
            "GITEA_PROFILE_NAME": "prgs-reconciler",
            # Force preflight purity to evaluate (disable test short-circuit).
            "GITEA_TEST_PORCELAIN": "",
        },
        clear=True,
    )
    @patch("mcp_server.get_profile", return_value=RECONCILER_PROFILE)
    def test_reconciler_role_bypasses_branches_only_mutation_guard(self, _profile):
        mcp_server.record_preflight_check("whoami")
        mcp_server.record_preflight_check(
            "capability",
            resolved_role="reconciler",
            resolved_task="reconcile_merged_cleanups",
        )

        with patch("mcp_server.PROJECT_ROOT", CONTROL_ROOT):
            with patch(
                "mcp_server.root_checkout_guard.resolve_remote_master_sha",
                return_value="abc123",
            ):
                with patch(
                    "mcp_server.issue_lock_worktree.read_worktree_git_state",
                    return_value={
                        "current_branch": "master",
                        "head_sha": "abc123",
                        "porcelain_status": "",
                    },
                ):
                    try:
                        mcp_server.verify_preflight_purity(
                            remote="prgs",
                            task="reconcile_merged_cleanups",
                        )
                    except RuntimeError as e:
                        self.fail(
                            "verify_preflight_purity raised RuntimeError "
                            f"unexpectedly for reconciler: {e}"
                        )

    @patch.dict(
        os.environ,
        {"GITEA_PROFILE_NAME": "prgs-author", "GITEA_TEST_PORCELAIN": ""},
        clear=True,
    )
    @patch("mcp_server.get_profile", return_value=AUTHOR_PROFILE)
    def test_author_role_remains_blocked_on_root_checkout(self, _profile):
        mcp_server.record_preflight_check("whoami")
        mcp_server.record_preflight_check(
            "capability",
            resolved_role="author",
            resolved_task="reconcile_merged_cleanups",
        )

        with patch("mcp_server.PROJECT_ROOT", CONTROL_ROOT):
            with patch(
                "mcp_server.root_checkout_guard.resolve_remote_master_sha",
                return_value="abc123",
            ):
                with patch(
                    "mcp_server.issue_lock_worktree.read_worktree_git_state",
                    return_value={
                        "current_branch": "master",
                        "head_sha": "abc123",
                        "porcelain_status": "",
                    },
                ):
                    with self.assertRaises(RuntimeError) as ctx:
                        mcp_server.verify_preflight_purity(
                            remote="prgs",
                            task="reconcile_merged_cleanups",
                        )
        message = str(ctx.exception)
        self.assertTrue(
            "stable control checkout" in message
            or "author mutation blocked" in message
            or "branches/" in message,
            msg=message,
        )

    @patch.dict(os.environ, {"GITEA_PROFILE_NAME": "prgs-reconciler"}, clear=True)
    @patch("mcp_server.get_profile", return_value=RECONCILER_PROFILE)
    def test_open_unmerged_pr_heads_are_not_safe_cleanup_targets(self, _profile):
        """AC5: active/unmerged author work must remain blocked from cleanup."""
        mcp_server.record_preflight_check("whoami")
        mcp_server.record_preflight_check(
            "capability",
            resolved_role="reconciler",
            resolved_task="reconcile_merged_cleanups",
        )

        open_pr = {
            "number": 200,
            "title": "active work",
            "body": "Closes #200",
            "merged": False,
            "state": "open",
            "head": {"ref": "feat/issue-200", "sha": "livehead1"},
        }
        # Same head branch still open on another PR while a closed/merged PR
        # used the same branch name historically — open head must block delete.
        closed_merged = {
            "number": 199,
            "title": "older merge same branch name",
            "body": "Closes #199",
            "merged": True,
            "merged_at": "2026-07-01T12:00:00Z",
            "merge_commit_sha": "deadbeef",
            "head": {"ref": "feat/issue-200", "sha": "oldhead99"},
        }
        self.mock_api.side_effect = [
            [closed_merged],  # closed
            [open_pr],  # open
        ]

        with patch("mcp_server._remote_branch_exists", return_value=True):
            with patch(
                "mcp_server.merged_cleanup_reconcile.is_head_ancestor_of_ref",
                return_value=True,
            ):
                result = mcp_server.gitea_reconcile_merged_cleanups(
                    dry_run=True,
                    remote="prgs",
                )
        self.assertTrue(result["success"])
        self.assertEqual(len(result["entries"]), 1)
        remote_assessment = result["entries"][0].get("remote_branch") or {}
        self.assertFalse(
            remote_assessment.get("safe_to_delete_remote"),
            msg=f"open head must block remote delete: {remote_assessment}",
        )


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