"""Tests for VNC connection management tools."""

import asyncio
import json
from contextlib import asynccontextmanager
from unittest.mock import AsyncMock, patch

import pytest
from fastmcp import Client

from mcvnc.server import mcp
from tests.conftest import _make_vnc_client


@asynccontextmanager
async def _mock_connect(*args, **kwargs):
    yield _make_vnc_client()


async def test_connect_creates_session(client: Client):
    result = await client.call_tool("vnc_list_sessions", {})
    data = json.loads(result.content[0].text)
    assert len(data) == 1
    assert data[0]["session_id"] == "testhost:5900"
    assert data[0]["width"] == 1920
    assert data[0]["height"] == 1080


async def test_connect_custom_session_id(client: Client):
    with patch("mcvnc._connect.asyncvnc2.connect", side_effect=_mock_connect):
        await client.call_tool("vnc_connect", {
            "host": "otherhost",
            "port": 5901,
            "session_id": "mydesktop",
        })
    result = await client.call_tool("vnc_list_sessions", {})
    data = json.loads(result.content[0].text)
    ids = [s["session_id"] for s in data]
    assert "mydesktop" in ids
    assert "testhost:5900" in ids


async def test_connect_duplicate_raises(client: Client):
    with patch("mcvnc._connect.asyncvnc2.connect", side_effect=_mock_connect):
        with pytest.raises(Exception, match="already exists"):
            await client.call_tool("vnc_connect", {"host": "testhost", "port": 5900})


async def test_disconnect(client: Client):
    result = await client.call_tool("vnc_disconnect", {"session_id": "testhost:5900"})
    assert "Disconnected" in result.content[0].text

    result = await client.call_tool("vnc_list_sessions", {})
    text = result.content[0].text
    assert "No active" in text or json.loads(text) == []


async def test_disconnect_nonexistent_raises(client: Client):
    with pytest.raises(Exception, match="No session"):
        await client.call_tool("vnc_disconnect", {"session_id": "nope"})


async def test_session_info(client: Client):
    result = await client.call_tool("vnc_session_info", {"session_id": "testhost:5900"})
    data = json.loads(result.content[0].text)
    assert data["host"] == "testhost"
    assert data["port"] == 5900
    assert data["desktop_name"] == "Test Desktop"
    assert data["color_mode"] == "rgba"


async def test_session_info_nonexistent_raises(client: Client):
    with pytest.raises(Exception, match="No session"):
        await client.call_tool("vnc_session_info", {"session_id": "nope"})


async def test_connect_timeout():
    """C1: Connection timeout produces a clear ToolError and cleans up."""

    async def _slow_aenter(self):
        await asyncio.sleep(999)

    @asynccontextmanager
    async def _mock_slow_connect(*args, **kwargs):
        yield  # pragma: no cover — never reached

    mock_cm = AsyncMock()
    mock_cm.__aenter__ = _slow_aenter
    mock_cm.__aexit__ = AsyncMock(return_value=False)

    with patch("mcvnc._connect.asyncvnc2.connect", return_value=mock_cm):
        with patch("mcvnc._connect.CONNECTION_TIMEOUT", 0.1):
            async with Client(mcp) as c:
                with pytest.raises(Exception, match="timed out"):
                    await c.call_tool("vnc_connect", {"host": "slow", "port": 5900})
                # Verify __aexit__ was called for cleanup
                mock_cm.__aexit__.assert_called_once()


async def test_connect_auth_failure():
    """C1: Auth failure calls __aexit__ to clean up the socket."""
    mock_cm = AsyncMock()
    mock_cm.__aenter__ = AsyncMock(side_effect=Exception("auth failed"))
    mock_cm.__aexit__ = AsyncMock(return_value=False)

    with patch("mcvnc._connect.asyncvnc2.connect", return_value=mock_cm):
        async with Client(mcp) as c:
            with pytest.raises(Exception, match="auth failed"):
                await c.call_tool("vnc_connect", {"host": "badauth", "port": 5900})
            mock_cm.__aexit__.assert_called_once()
