"""Tests for gitea_delete_branch capability gate (Issue #408).

``gitea_delete_branch`` requires the exact ``gitea.branch.delete`` operation:
without it the delete fails closed (no preflight, no auth lookup, no API call,
structured permission report). With it, deletion proceeds through existing
preflight and audit unchanged.
"""
import json
import os
import sys
import tempfile
import unittest
from unittest.mock import patch

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

import mcp_server
from mcp_server import gitea_delete_branch

FAKE_AUTH = "token fake"

AUTHOR_NO_DELETE = {
    "profile_name": "prgs-author",
    "allowed_operations": [
        "gitea.read", "gitea.pr.create", "gitea.pr.comment",
        "gitea.branch.push", "gitea.issue.comment",
    ],
    "forbidden_operations": ["gitea.pr.approve", "gitea.pr.merge"],
    "audit_label": "prgs-author",
}

AUTHOR_WITH_DELETE = {
    "profile_name": "prgs-author-deleter",
    "allowed_operations": AUTHOR_NO_DELETE["allowed_operations"] + [
        "gitea.branch.delete",
    ],
    "forbidden_operations": ["gitea.pr.approve", "gitea.pr.merge"],
    "audit_label": "prgs-author-deleter",
}

CONFIG = {
    "version": 2,
    "contexts": {
        "ctx": {
            "enabled": True,
            "gitea": {"enabled": True, "base_url": "https://gitea.example.com"},
        }
    },
    "profiles": {
        "author-no-delete": {
            "enabled": True,
            "context": "ctx",
            "role": "author",
            "username": "author-user",
            "auth": {"type": "env", "name": "GITEA_TOKEN_AUTHOR"},
            "allowed_operations": AUTHOR_NO_DELETE["allowed_operations"],
            "forbidden_operations": [],
            "execution_profile": "author-no-delete",
        },
        "author-with-delete": {
            "enabled": True,
            "context": "ctx",
            "role": "author",
            "username": "deleter-user",
            "auth": {"type": "env", "name": "GITEA_TOKEN_AUTHOR"},
            "allowed_operations": AUTHOR_WITH_DELETE["allowed_operations"],
            "forbidden_operations": [],
            "execution_profile": "author-with-delete",
        },
        "reviewer-profile": {
            "enabled": True,
            "context": "ctx",
            "role": "reviewer",
            "username": "reviewer-user",
            "auth": {"type": "env", "name": "GITEA_TOKEN_REVIEWER"},
            "allowed_operations": [
                "gitea.read", "gitea.pr.review", "gitea.pr.merge",
            ],
            "forbidden_operations": [
                "gitea.branch.delete", "gitea.branch.push", "gitea.pr.create",
            ],
            "execution_profile": "reviewer-profile",
        },
    },
    "rules": {"allow_runtime_switching": False},
}


class TestDeleteBranchToolGate(unittest.TestCase):

    def setUp(self):
        self._preflight_snapshot = (
            mcp_server._preflight_whoami_called,
            mcp_server._preflight_capability_called,
        )
        mcp_server._preflight_whoami_called = False
        mcp_server._preflight_capability_called = False
        self._remotes = patch.dict(mcp_server.REMOTES, {
            "prgs": {"host": "gitea.example.com", "org": "Example-Org",
                     "repo": "Example-Repo"},
        })
        self._remotes.start()
        mcp_server._IDENTITY_CACHE.clear()
        patch("gitea_config.load_config", return_value={}).start()
        patch(
            "gitea_config.is_runtime_switching_enabled", return_value=False
        ).start()
        patch("gitea_audit.audit_enabled", return_value=False).start()
        self.mock_api = patch("mcp_server.api_request").start()
        self.mock_auth = patch(
            "mcp_server.get_auth_header", return_value=FAKE_AUTH
        ).start()

    def tearDown(self):
        patch.stopall()
        mcp_server._IDENTITY_CACHE.clear()
        mcp_server._preflight_whoami_called, mcp_server._preflight_capability_called = (
            self._preflight_snapshot
        )

    def _set_profile(self, profile):
        patch("mcp_server.get_profile", return_value=profile).start()

    def test_blocked_without_delete_capability(self):
        self._set_profile(AUTHOR_NO_DELETE)
        res = gitea_delete_branch(branch="feat/branch", remote="prgs")
        self.assertFalse(res["success"])
        self.assertFalse(res["performed"])
        self.assertEqual(res["required_permission"], "gitea.branch.delete")
        self.assertTrue(res["reasons"])
        self.assertEqual(
            res["permission_report"]["missing_permission"],
            "gitea.branch.delete",
        )
        self.mock_api.assert_not_called()
        self.mock_auth.assert_not_called()

    def test_allowed_delete_proceeds(self):
        self._set_profile(AUTHOR_WITH_DELETE)
        mcp_server.record_preflight_check("whoami")
        mcp_server.record_preflight_check("capability", resolved_role="author")
        res = gitea_delete_branch(branch="feat/branch", remote="prgs")
        self.assertTrue(res["success"])
        self.assertIn("deleted", res["message"])
        delete_calls = [
            c for c in self.mock_api.call_args_list if c.args[0] == "DELETE"
        ]
        self.assertTrue(delete_calls)

    def test_allowed_delete_audited_with_capability_proof(self):
        self._set_profile(AUTHOR_WITH_DELETE)
        patch("gitea_audit.audit_enabled", return_value=True).start()
        mock_write = patch("gitea_audit.write_event").start()
        mcp_server.record_preflight_check("whoami")
        mcp_server.record_preflight_check("capability", resolved_role="author")
        self.mock_api.return_value = {}
        gitea_delete_branch(branch="feat/branch", remote="prgs")
        mock_write.assert_called()
        event = mock_write.call_args[0][0]
        self.assertEqual(event["action"], "delete_branch")
        self.assertEqual(
            event["request_metadata"]["required_permission"],
            "gitea.branch.delete",
        )


class TestDeleteBranchResolverParity(unittest.TestCase):
    def setUp(self):
        self._remotes = patch.dict(mcp_server.REMOTES, {
            "prgs": {"host": "gitea.example.com", "org": "Example-Org",
                     "repo": "Example-Repo"},
        })
        self._remotes.start()
        mcp_server._IDENTITY_CACHE.clear()
        self._dir = tempfile.TemporaryDirectory()
        self.config_path = os.path.join(self._dir.name, "profiles.json")
        with open(self.config_path, "w", encoding="utf-8") as fh:
            fh.write(json.dumps(CONFIG))
        patch(
            "gitea_config.is_runtime_switching_enabled", return_value=False
        ).start()
        patch("gitea_audit.audit_enabled", return_value=False).start()
        self.mock_api = patch("mcp_server.api_request").start()
        patch("mcp_server.get_auth_header", return_value=FAKE_AUTH).start()

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

    def _env(self, profile: str) -> dict:
        return {
            "GITEA_MCP_CONFIG": self.config_path,
            "GITEA_MCP_PROFILE": profile,
            "GITEA_TOKEN_AUTHOR": "author-pass",
            "GITEA_TOKEN_REVIEWER": "reviewer-pass",
        }

    def test_reviewer_resolver_denial_blocks_raw_tool(self):
        with patch.dict(os.environ, self._env("reviewer-profile"), clear=True):
            resolve = mcp_server.gitea_resolve_task_capability(
                task="delete_branch", remote="prgs")
            self.assertFalse(resolve["allowed_in_current_session"])
            res = gitea_delete_branch(branch="feat/branch", remote="prgs")
        self.assertFalse(res["success"])
        self.assertEqual(
            res["permission_report"]["missing_permission"],
            resolve["required_operation_permission"],
        )
        delete_calls = [
            c for c in self.mock_api.call_args_list if c.args[0] == "DELETE"
        ]
        self.assertFalse(delete_calls)


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