import os
import unittest
from unittest.mock import patch, MagicMock
from datetime import datetime

import gitea_mcp_server

class TestMcpStaleRuntime(unittest.TestCase):
    @patch("subprocess.run")
    @patch("os.path.getmtime")
    @patch("os.path.exists")
    @patch("os.getpid")
    @patch.dict("os.environ", {"GITEA_FORCE_MCP_RUNTIME_CHECK": "1"})
    def test_stale_and_missing_runtimes(self, mock_getpid, mock_exists, mock_getmtime, mock_run):
        # Setup mocks
        mock_getpid.return_value = 12345
        mock_exists.return_value = True
        
        # Code modification time: Jul 8 2026, 14:00:00
        code_time = datetime(2026, 7, 8, 14, 0, 0)
        mock_getmtime.return_value = code_time.timestamp()
        
        # Mock ps -ax output
        # PID 12345 is self (started at 13:00:00 - stale)
        # PID 54321 is prgs-author (started at 15:00:00 - fresh)
        # prgs-reviewer is missing
        ps_output = (
            "  PID LSTART COMMAND\n"
            "12345 Wed Jul  8 13:00:00 2026 /path/to/python mcp_server.py\n"
            "54321 Wed Jul  8 15:00:00 2026 /path/to/python mcp_server.py\n"
        )
        
        mock_run_ps = MagicMock()
        mock_run_ps.stdout = ps_output
        
        # Mock env output for ps eww
        mock_run_env12345 = MagicMock()
        mock_run_env12345.stdout = "GITEA_MCP_PROFILE=prgs-reconciler"
        
        mock_run_env54321 = MagicMock()
        mock_run_env54321.stdout = "GITEA_MCP_PROFILE=prgs-author"
        
        def side_effect(args, **kwargs):
            if args[0] == "ps" and "eww" in args:
                pid = args[2]
                if pid == "12345":
                    return mock_run_env12345
                elif pid == "54321":
                    return mock_run_env54321
            elif args[0] == "ps":
                return mock_run_ps
            raise ValueError(f"Unexpected subprocess run args: {args}")
            
        mock_run.side_effect = side_effect
        
        # Test 1: required reviewer role matching prgs-reviewer (which is missing)
        reasons = gitea_mcp_server._check_mcp_runtimes_diagnostics("review_pr", ["prgs-reviewer"])
        
        self.assertTrue(any("stale-runtime: The active Gitea MCP server process is stale" in r for r in reasons))
        self.assertTrue(any("stale-runtime: None of the matching profiles for task" in r for r in reasons))
        
        # Test 2: required author role matching prgs-author (which is fresh)
        reasons_author = gitea_mcp_server._check_mcp_runtimes_diagnostics("create_issue", ["prgs-author"])
        
        # Still contains self stale error
        self.assertTrue(any("stale-runtime: The active Gitea MCP server process is stale" in r for r in reasons_author))
        # But does NOT contain missing author profile error
        self.assertFalse(any("None of the matching profiles for task" in r for r in reasons_author))

    @patch("subprocess.run")
    @patch("os.path.getmtime")
    @patch("os.path.exists")
    @patch("os.getpid")
    @patch.dict("os.environ", {"GITEA_FORCE_MCP_RUNTIME_CHECK": "1"})
    def test_git_head_change_triggers_staleness(self, mock_getpid, mock_exists, mock_getmtime, mock_run):
        # Set boot SHA to FAKE1 and current to FAKE2
        gitea_mcp_server._process_boot_head_sha = "FAKE1"
        mock_getpid.return_value = 12345
        mock_exists.return_value = True

        # Code time is in the past (fresh process chronologically)
        code_time = datetime(2026, 7, 8, 11, 0, 0)
        mock_getmtime.return_value = code_time.timestamp()

        ps_output = (
            "  PID LSTART COMMAND\n"
            "12345 Wed Jul  8 12:00:00 2026 /path/to/python mcp_server.py\n"
        )

        mock_run_ps = MagicMock()
        mock_run_ps.stdout = ps_output

        mock_run_env = MagicMock()
        mock_run_env.stdout = "GITEA_MCP_PROFILE=prgs-author"

        mock_run_git = MagicMock()
        mock_run_git.stdout = "FAKE2" # different SHA

        def side_effect(args, **kwargs):
            if args[0] == "ps" and "eww" in args:
                return mock_run_env
            elif args[0] == "ps":
                return mock_run_ps
            elif args[0] == "git" and "rev-parse" in args:
                return mock_run_git
            raise ValueError(f"Unexpected: {args}")

        mock_run.side_effect = side_effect

        # Stale even though process started AFTER code mtime, because git HEAD changed
        reasons = gitea_mcp_server._check_mcp_runtimes_diagnostics("create_issue", ["prgs-author"])
        self.assertTrue(any("stale-runtime: The active Gitea MCP server process is stale" in r for r in reasons))

    def test_auto_restart_helper_removed_from_read_only_path(self):
        """#685: config-touch / os._exit self-recovery is no longer on the server."""
        self.assertFalse(
            hasattr(gitea_mcp_server, "_trigger_mcp_auto_restart"),
            "_trigger_mcp_auto_restart must not remain (side-effect-free resolver)",
        )
        self.assertFalse(hasattr(gitea_mcp_server, "_restart_triggered"))


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