import sys as _sys
from pathlib import Path as _Path
_sys.path.insert(0, str(_Path(__file__).resolve().parent))
from mutation_profile_fixture import shared_mutation_env  # noqa: E402
import os
import sys
import unittest
from pathlib import Path
from unittest.mock import MagicMock, patch

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

import gitea_mcp_server as srv


FAKE_AUTH = {"Authorization": "token test-token"}
current_file_path = Path(__file__).resolve()
if "branches" in current_file_path.parts:
    CONTROL_CHECKOUT_ROOT = str(current_file_path.parents[3])
else:
    CONTROL_CHECKOUT_ROOT = str(current_file_path.parents[1])


class TestIssueCommentWorkspaceGuard(unittest.TestCase):
    AUTHOR_ENV = {
        "GITEA_PROFILE_NAME": "gitea-author",
        "GITEA_ALLOWED_OPERATIONS": "gitea.read,gitea.issue.comment",
    }

    def setUp(self):
        srv._preflight_whoami_called = True
        srv._preflight_capability_called = True
        srv._preflight_resolved_role = "author"
        srv._preflight_resolved_task = "comment_issue"
        srv._preflight_whoami_violation = False
        srv._preflight_capability_violation = False
        srv._preflight_reviewer_violation_files = []

        self._orig_in_test = srv._preflight_in_test_mode
        srv._preflight_in_test_mode = lambda: False
        self.addCleanup(self._restore_preflight_mode)

    def _restore_preflight_mode(self):
        srv._preflight_in_test_mode = self._orig_in_test

    def _git_state(self, valid_worktree: str):
        def side_effect(path):
            real = os.path.realpath(path)
            if real == os.path.realpath(CONTROL_CHECKOUT_ROOT):
                return {
                    "current_branch": "master",
                    "head_sha": "a" * 40,
                    "porcelain_status": "",
                }
            if real == os.path.realpath(valid_worktree):
                return {
                    "current_branch": "fix/issue-560-issue-comment-worktree-path",
                    "head_sha": "b" * 40,
                    "porcelain_status": "",
                }
            return {
                "current_branch": "other",
                "head_sha": "c" * 40,
                "porcelain_status": "",
            }

        return side_effect

    def _subprocess(self, valid_worktree: str, outside_worktree: str | None = None):
        def side_effect(cmd, *args, **kwargs):
            result = MagicMock(returncode=0, stdout="")
            cwd = ""
            if isinstance(cmd, list) and "-C" in cmd:
                cwd = os.path.realpath(cmd[cmd.index("-C") + 1])

            if isinstance(cmd, list) and "--show-toplevel" in cmd:
                if cwd == os.path.realpath(valid_worktree):
                    result.stdout = f"{valid_worktree}\n"
                elif outside_worktree and cwd == os.path.realpath(outside_worktree):
                    result.stdout = f"{outside_worktree}\n"
                else:
                    result.stdout = f"{CONTROL_CHECKOUT_ROOT}\n"
                return result

            if isinstance(cmd, list) and "--git-common-dir" in cmd:
                if cwd == os.path.realpath(valid_worktree):
                    result.stdout = f"{CONTROL_CHECKOUT_ROOT}/.git\n"
                elif outside_worktree and cwd == os.path.realpath(outside_worktree):
                    result.stdout = "/tmp/other-repo/.git\n"
                else:
                    result.stdout = f"{CONTROL_CHECKOUT_ROOT}/.git\n"
                return result

            return result

        return side_effect

    @patch("gitea_mcp_server._auth", return_value=FAKE_AUTH)
    @patch("gitea_mcp_server.api_request")
    @patch("gitea_mcp_server.root_checkout_guard.resolve_remote_master_sha", return_value="a" * 40)
    def test_comment_rejects_control_checkout_without_worktree_path(
        self, _remote_sha, mock_api, _auth
    ):
        valid_worktree = os.path.join(
            CONTROL_CHECKOUT_ROOT,
            "branches",
            "issue-560-issue-comment-worktree-path",
        )
        mock_api.return_value = {"id": 42}
        with patch.object(srv, "PROJECT_ROOT", CONTROL_CHECKOUT_ROOT):
            with patch(
                "gitea_mcp_server.issue_lock_worktree.read_worktree_git_state",
                side_effect=self._git_state(valid_worktree),
            ):
                try:
                    res = srv.gitea_create_issue_comment(
                        issue_number=557,
                        body="evidence comment",
                        remote="prgs",
                    )
                except RuntimeError as exc:
                    self.assertIn("stable control checkout", str(exc))
                else:
                    # #683 typed blocker at mutation entrypoint
                    self.assertFalse(res.get("success"))
                    self.assertFalse(res.get("performed"))
                    blob = " ".join(res.get("reasons") or [])
                    self.assertTrue(
                        "stable control checkout" in blob
                        or res.get("blocker_kind")
                    )
                    self.assertTrue(res.get("exact_next_action") or res.get("reasons"))
        mock_api.assert_not_called()

    @patch("gitea_mcp_server._auth", return_value=FAKE_AUTH)
    @patch("gitea_mcp_server.api_request")
    @patch("gitea_mcp_server.root_checkout_guard.resolve_remote_master_sha", return_value="a" * 40)
    @patch("os.path.isdir", return_value=True)
    @patch("os.path.exists", return_value=True)
    def test_comment_accepts_valid_branches_worktree_path_with_explicit_repo(
        self, _exists, _isdir, _remote_sha, mock_api, _auth
    ):
        valid_worktree = os.path.join(
            CONTROL_CHECKOUT_ROOT,
            "branches",
            "issue-560-issue-comment-worktree-path",
        )
        mock_api.return_value = {"id": 43}
        with patch.object(srv, "PROJECT_ROOT", CONTROL_CHECKOUT_ROOT):
            with patch("gitea_mcp_server._get_workspace_porcelain", return_value=""):
                with patch(
                    "gitea_mcp_server.issue_lock_worktree.read_worktree_git_state",
                    side_effect=self._git_state(valid_worktree),
                ):
                    with patch(
                        "gitea_mcp_server.subprocess.run",
                        side_effect=self._subprocess(valid_worktree),
                    ):
                        with patch.dict(os.environ, self.AUTHOR_ENV, clear=True):
                            result = srv.gitea_create_issue_comment(
                                issue_number=557,
                                body="evidence comment",
                                remote="prgs",
                                org="Scaled-Tech-Consulting",
                                repo="Gitea-Tools",
                                worktree_path=valid_worktree,
                            )

        self.assertTrue(result["success"])
        self.assertTrue(result["performed"])
        self.assertEqual(result["comment_id"], 43)
        method, url, _auth_arg, payload = mock_api.call_args[0]
        self.assertEqual(method, "POST")
        self.assertIn("/repos/Scaled-Tech-Consulting/Gitea-Tools/issues/557/comments", url)
        self.assertEqual(payload, {"body": "evidence comment"})

    @patch("gitea_mcp_server._auth", return_value=FAKE_AUTH)
    @patch("gitea_mcp_server.api_request")
    @patch("gitea_mcp_server.root_checkout_guard.resolve_remote_master_sha", return_value="a" * 40)
    @patch("os.path.isdir", return_value=True)
    @patch("os.path.exists", return_value=True)
    def test_comment_rejects_outside_repo_worktree_path(
        self, _exists, _isdir, _remote_sha, mock_api, _auth
    ):
        valid_worktree = os.path.join(
            CONTROL_CHECKOUT_ROOT,
            "branches",
            "issue-560-issue-comment-worktree-path",
        )
        outside_worktree = "/tmp/not-gitea-tools-worktree"
        with patch.object(srv, "PROJECT_ROOT", CONTROL_CHECKOUT_ROOT):
            with patch("gitea_mcp_server._get_workspace_porcelain", return_value=""):
                with patch(
                    "gitea_mcp_server.issue_lock_worktree.read_worktree_git_state",
                    side_effect=self._git_state(valid_worktree),
                ):
                    with patch(
                        "gitea_mcp_server.subprocess.run",
                        side_effect=self._subprocess(valid_worktree, outside_worktree),
                    ):
                        with patch.dict(os.environ, self.AUTHOR_ENV, clear=True):
                            try:
                                res = srv.gitea_create_issue_comment(
                                    issue_number=557,
                                    body="evidence comment",
                                    remote="prgs",
                                    worktree_path=outside_worktree,
                                )
                            except RuntimeError as exc:
                                self.assertIn(
                                    "does not belong to the target repository",
                                    str(exc),
                                )
                            else:
                                self.assertFalse(res.get("success"))
                                blob = " ".join(res.get("reasons") or [])
                                self.assertIn(
                                    "does not belong to the target repository", blob
                                )
        mock_api.assert_not_called()

    @patch("gitea_mcp_server._auth", return_value=FAKE_AUTH)
    @patch("gitea_mcp_server.api_request")
    def test_comment_still_requires_capability_preflight(self, mock_api, _auth):
        srv._preflight_capability_called = False
        with patch.object(srv, "PROJECT_ROOT", CONTROL_CHECKOUT_ROOT):
            with self.assertRaises(RuntimeError) as ctx:
                srv.gitea_create_issue_comment(
                    issue_number=557,
                    body="evidence comment",
                    remote="prgs",
                    worktree_path=os.path.join(
                        CONTROL_CHECKOUT_ROOT,
                        "branches",
                        "issue-560-issue-comment-worktree-path",
                    ),
                )
        self.assertIn("Task capability", str(ctx.exception))
        mock_api.assert_not_called()

    @patch("gitea_mcp_server._auth", return_value=FAKE_AUTH)
    @patch("gitea_mcp_server.api_request")
    @patch("gitea_mcp_server.root_checkout_guard.resolve_remote_master_sha", return_value="a" * 40)
    @patch("os.path.isdir", return_value=True)
    @patch("os.path.exists", return_value=True)
    def test_comment_still_requires_issue_comment_permission(
        self, _exists, _isdir, _remote_sha, mock_api, _auth
    ):
        valid_worktree = os.path.join(
            CONTROL_CHECKOUT_ROOT,
            "branches",
            "issue-560-issue-comment-worktree-path",
        )
        denied_env = {
            "GITEA_PROFILE_NAME": "gitea-author",
            "GITEA_ALLOWED_OPERATIONS": "gitea.read,gitea.pr.comment",
        }
        with patch.object(srv, "PROJECT_ROOT", CONTROL_CHECKOUT_ROOT):
            with patch("gitea_mcp_server._get_workspace_porcelain", return_value=""):
                with patch(
                    "gitea_mcp_server.issue_lock_worktree.read_worktree_git_state",
                    side_effect=self._git_state(valid_worktree),
                ):
                    with patch(
                        "gitea_mcp_server.subprocess.run",
                        side_effect=self._subprocess(valid_worktree),
                    ):
                        with patch.dict(os.environ, denied_env, clear=True):
                            result = srv.gitea_create_issue_comment(
                                issue_number=557,
                                body="evidence comment",
                                remote="prgs",
                                worktree_path=valid_worktree,
                            )
        self.assertFalse(result["success"])
        self.assertFalse(result["performed"])
        self.assertIn("permission_report", result)
        self.assertEqual(
            result["permission_report"]["missing_permission"],
            "gitea.issue.comment",
        )
        mock_api.assert_not_called()


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