"""Tests for runtime_context / mutation-guard workspace alignment (#460)."""

from __future__ import annotations

import os
import sys
import unittest
from pathlib import Path
from unittest import mock
from unittest.mock import MagicMock

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

import author_mutation_worktree as amw  # noqa: E402
import gitea_mcp_server as srv  # noqa: E402

current_file_path = Path(__file__).resolve()
if "branches" in current_file_path.parts:
    CONTROL_ROOT = str(current_file_path.parents[3])
    BRANCHES_WORKTREE = str(current_file_path.parents[1])
else:
    CONTROL_ROOT = str(current_file_path.parents[1])
    BRANCHES_WORKTREE = str(current_file_path.parents[1] / "branches" / "mock-worktree")
MCP_PROCESS_ROOT = BRANCHES_WORKTREE


class TestCanonicalRepoRoot(unittest.TestCase):
    @mock.patch("subprocess.run")
    def test_resolves_main_repo_from_branches_worktree(self, mock_run):
        mock_run.return_value = MagicMock(
            returncode=0,
            stdout=f"{CONTROL_ROOT}/.git\n",
        )
        root = amw.resolve_canonical_repo_root(BRANCHES_WORKTREE, MCP_PROCESS_ROOT)
        self.assertEqual(root, CONTROL_ROOT)

    def test_falls_back_when_git_unavailable(self):
        root = amw.resolve_canonical_repo_root("/missing/path", MCP_PROCESS_ROOT)
        self.assertEqual(root, os.path.realpath(MCP_PROCESS_ROOT))


class TestWorkspaceRepoMembership(unittest.TestCase):
    @mock.patch("os.path.isdir", return_value=True)
    @mock.patch("os.path.exists", return_value=True)
    @mock.patch("subprocess.run")
    def test_valid_branches_worktree_accepted(self, mock_run, *_exists):
        mock_run.return_value = MagicMock(
            returncode=0,
            stdout=f"{CONTROL_ROOT}/.git\n",
        )
        result = amw.assess_workspace_repo_membership(
            workspace_path=BRANCHES_WORKTREE,
            canonical_repo_root=CONTROL_ROOT,
        )
        self.assertTrue(result["proven"])
        self.assertFalse(result["block"])

    @mock.patch("os.path.isdir", return_value=True)
    @mock.patch("os.path.exists", return_value=True)
    @mock.patch("subprocess.run")
    def test_wrong_repo_rejected(self, mock_run, *_exists):
        mock_run.return_value = MagicMock(
            returncode=0,
            stdout="/other/repo/.git\n",
        )
        result = amw.assess_workspace_repo_membership(
            workspace_path=BRANCHES_WORKTREE,
            canonical_repo_root=CONTROL_ROOT,
        )
        self.assertTrue(result["block"])
        self.assertIn("does not belong", result["reasons"][0])

    @mock.patch("os.path.exists", return_value=False)
    def test_missing_worktree_rejected(self, *_exists):
        result = amw.assess_workspace_repo_membership(
            workspace_path=f"{CONTROL_ROOT}/branches/missing-worktree",
            canonical_repo_root=CONTROL_ROOT,
        )
        self.assertTrue(result["block"])
        self.assertIn("does not exist", result["reasons"][0])


class TestRuntimeContextGuardAlignment(unittest.TestCase):
    def setUp(self):
        srv._preflight_whoami_called = True
        srv._preflight_capability_called = True
        srv._preflight_resolved_role = "author"
        self._orig_in_test = srv._preflight_in_test_mode
        srv._preflight_in_test_mode = lambda: False
        self._env_patch = mock.patch.dict(
            os.environ,
            {},
            clear=False,
        )
        self._env_patch.start()
        os.environ.pop("GITEA_ACTIVE_WORKTREE", None)
        os.environ.pop("GITEA_AUTHOR_WORKTREE", None)

    def tearDown(self):
        srv._preflight_in_test_mode = self._orig_in_test
        self._env_patch.stop()

    @mock.patch("subprocess.run")
    @mock.patch("os.path.isdir", return_value=True)
    @mock.patch("os.path.exists", return_value=True)
    def test_runtime_context_and_guard_share_resolved_workspace(
        self, _exists, _isdir, mock_run
    ):
        def run_side_effect(cmd, *args, **kwargs):
            res = MagicMock(returncode=0)
            if "--git-common-dir" in cmd:
                res.stdout = f"{CONTROL_ROOT}/.git\n"
            elif "--show-toplevel" in cmd:
                cwd = cmd[cmd.index("-C") + 1] if "-C" in cmd else ""
                res.stdout = f"{cwd}\n"
            else:
                res.stdout = f"{CONTROL_ROOT}\n"
            return res
        mock_run.side_effect = run_side_effect
        with mock.patch.object(srv, "PROJECT_ROOT", MCP_PROCESS_ROOT):
            ctx = srv._resolve_author_mutation_context(BRANCHES_WORKTREE)
            status = srv.assess_preflight_status(worktree_path=BRANCHES_WORKTREE)
        self.assertEqual(ctx["workspace_path"], os.path.realpath(BRANCHES_WORKTREE))
        self.assertEqual(ctx["canonical_repo_root"], CONTROL_ROOT)
        self.assertFalse(ctx["roots_aligned"])
        self.assertEqual(
            status["preflight_workspace"]["active_task_workspace_root"],
            os.path.realpath(BRANCHES_WORKTREE),
        )
        self.assertEqual(
            status["preflight_workspace"]["canonical_repository_root"],
            CONTROL_ROOT,
        )
        self.assertIn("workspace_root_mismatch", status["preflight_workspace"])

    @mock.patch("subprocess.run")
    @mock.patch("os.path.isdir", return_value=True)
    @mock.patch("os.path.exists", return_value=True)
    def test_declared_branches_worktree_passes_when_mcp_root_differs(
        self, _exists, _isdir, mock_run
    ):
        mock_run.return_value = MagicMock(
            returncode=0,
            stdout=f"{CONTROL_ROOT}/.git\n",
        )
        with mock.patch.object(srv, "PROJECT_ROOT", MCP_PROCESS_ROOT):
            with mock.patch.dict("os.environ", {"GITEA_TEST_PORCELAIN": ""}, clear=False):
                srv.verify_preflight_purity(worktree_path=BRANCHES_WORKTREE)

    @mock.patch("gitea_mcp_server.root_checkout_guard.resolve_remote_master_sha", return_value="a" * 40)
    @mock.patch(
        "gitea_mcp_server.issue_lock_worktree.read_worktree_git_state",
        return_value={
            "current_branch": "master",
            "head_sha": "a" * 40,
            "porcelain_status": "",
        },
    )
    def test_stable_checkout_still_rejected(self, _git, _remote_sha):
        with mock.patch.object(srv, "PROJECT_ROOT", CONTROL_ROOT):
            with mock.patch.dict("os.environ", {"GITEA_TEST_PORCELAIN": ""}, clear=False):
                with self.assertRaises(RuntimeError) as ctx:
                    srv.verify_preflight_purity()
        self.assertIn("stable control checkout", str(ctx.exception))

    @mock.patch("gitea_mcp_server.root_checkout_guard.resolve_remote_master_sha", return_value="a" * 40)
    @mock.patch(
        "gitea_mcp_server.issue_lock_worktree.read_worktree_git_state",
        return_value={
            "current_branch": "master",
            "head_sha": "a" * 40,
            "porcelain_status": "",
        },
    )
    @mock.patch("os.path.isdir", return_value=True)
    @mock.patch("os.path.exists", return_value=True)
    @mock.patch("subprocess.run")
    def test_non_branches_worktree_rejected(
        self, mock_run, mock_exists, mock_isdir, _git, _remote_sha,
    ):
        outside = "/tmp/outside-repo-checkout"
        mock_run.return_value = MagicMock(
            returncode=0,
            stdout=f"{CONTROL_ROOT}/.git\n",
        )
        with mock.patch.object(srv, "PROJECT_ROOT", CONTROL_ROOT):
            with mock.patch.dict("os.environ", {"GITEA_TEST_PORCELAIN": ""}, clear=False):
                with self.assertRaises(RuntimeError) as ctx:
                    srv.verify_preflight_purity(worktree_path=outside)
        self.assertIn("not under", str(ctx.exception))


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