from unittest.mock import AsyncMock, MagicMock, patch

import pytest


@pytest.mark.asyncio
async def test_register_real_session_id_moves_pending_to_active_and_maps():
    from free_claude_code.cli.managed.manager import ManagedClaudeSessionManager

    with patch(
        "free_claude_code.cli.managed.manager.ManagedClaudeSession"
    ) as mock_session_cls:
        mock_session = MagicMock()
        mock_session.is_busy = False
        mock_session.stop = AsyncMock(return_value=True)
        mock_session_cls.return_value = mock_session

        manager = ManagedClaudeSessionManager(
            workspace_path="/tmp",
            proxy_root_url="http://x",
            auth_token="proxy-token",
        )
        session, temp_id, is_new = await manager.get_or_create_session()
        assert session is mock_session
        assert is_new is True
        mock_session_cls.assert_called_once()
        assert mock_session_cls.call_args.kwargs["auth_token"] == "proxy-token"

        ok = await manager.register_real_session_id(temp_id, "real_1")
        assert ok is True

        # Lookup via temp id should resolve to the real session id.
        s2, sid2, is_new2 = await manager.get_or_create_session(session_id=temp_id)
        assert s2 is mock_session
        assert sid2 == "real_1"
        assert is_new2 is False


@pytest.mark.asyncio
async def test_register_real_session_id_missing_temp_id_returns_false():
    from free_claude_code.cli.managed.manager import ManagedClaudeSessionManager

    manager = ManagedClaudeSessionManager(
        workspace_path="/tmp", proxy_root_url="http://x"
    )
    ok = await manager.register_real_session_id("missing", "real_1")
    assert ok is False


@pytest.mark.asyncio
async def test_remove_session_pending_stops_and_returns_true():
    from free_claude_code.cli.managed.manager import ManagedClaudeSessionManager

    with patch(
        "free_claude_code.cli.managed.manager.ManagedClaudeSession"
    ) as mock_session_cls:
        mock_session = MagicMock()
        mock_session.is_busy = False
        mock_session.stop = AsyncMock(return_value=True)
        mock_session_cls.return_value = mock_session

        manager = ManagedClaudeSessionManager(
            workspace_path="/tmp", proxy_root_url="http://x"
        )
        _, temp_id, _ = await manager.get_or_create_session()

        removed = await manager.remove_session(temp_id)
        assert removed is True
        mock_session.stop.assert_awaited_once()


@pytest.mark.asyncio
async def test_remove_session_active_removes_temp_mapping():
    from free_claude_code.cli.managed.manager import ManagedClaudeSessionManager

    with patch(
        "free_claude_code.cli.managed.manager.ManagedClaudeSession"
    ) as mock_session_cls:
        mock_session = MagicMock()
        mock_session.is_busy = False
        mock_session.stop = AsyncMock(return_value=True)
        mock_session_cls.return_value = mock_session

        manager = ManagedClaudeSessionManager(
            workspace_path="/tmp", proxy_root_url="http://x"
        )
        _, temp_id, _ = await manager.get_or_create_session()
        await manager.register_real_session_id(temp_id, "real_1")

        removed = await manager.remove_session("real_1")
        assert removed is True

        # Temp ID should no longer resolve to an active session after removal.
        _, sid2, is_new2 = await manager.get_or_create_session(session_id=temp_id)
        assert sid2 == temp_id
        assert is_new2 is True


@pytest.mark.asyncio
async def test_stop_all_reports_and_retains_stop_exceptions():
    from free_claude_code.cli.managed.manager import ManagedClaudeSessionManager

    manager = ManagedClaudeSessionManager(
        workspace_path="/tmp", proxy_root_url="http://x"
    )

    s1 = MagicMock()
    s1.stop = AsyncMock(side_effect=RuntimeError("boom"))
    s1.is_busy = False

    s2 = MagicMock()
    s2.stop = AsyncMock(return_value=True)
    s2.is_busy = False

    manager._sessions["a"] = s1
    manager._pending_sessions["b"] = s2

    with pytest.raises(
        RuntimeError,
        match=r"^Managed Claude session shutdown failures: 1\.$",
    ):
        await manager.stop_all()
    s1.stop.assert_awaited_once()
    s2.stop.assert_awaited_once()
    assert manager.get_stats()["active_sessions"] == 1
    assert manager.get_stats()["pending_sessions"] == 0
