"""Tests for app.core.websocket — ConnectionManager and ws_manager singleton"""

import pytest
from unittest.mock import AsyncMock, MagicMock, patch


@pytest.fixture
def manager():
    from app.core.websocket import ConnectionManager
    return ConnectionManager()


@pytest.mark.asyncio
async def test_connect(manager):
    mock_ws = AsyncMock()
    await manager.connect("user-1", mock_ws)
    mock_ws.accept.assert_called_once()
    assert "user-1" in manager._connections
    assert mock_ws in manager._connections["user-1"]


@pytest.mark.asyncio
async def test_connect_multiple_same_user(manager):
    ws1, ws2 = AsyncMock(), AsyncMock()
    await manager.connect("user-1", ws1)
    await manager.connect("user-1", ws2)
    assert len(manager._connections["user-1"]) == 2


@pytest.mark.asyncio
async def test_disconnect(manager):
    mock_ws = AsyncMock()
    await manager.connect("user-1", mock_ws)
    await manager.disconnect("user-1", mock_ws)
    assert "user-1" not in manager._connections


@pytest.mark.asyncio
async def test_disconnect_removes_one_of_many(manager):
    ws1, ws2 = AsyncMock(), AsyncMock()
    await manager.connect("user-1", ws1)
    await manager.connect("user-1", ws2)
    await manager.disconnect("user-1", ws1)
    assert "user-1" in manager._connections
    assert len(manager._connections["user-1"]) == 1
    assert ws2 in manager._connections["user-1"]


@pytest.mark.asyncio
async def test_disconnect_nonexistent_user(manager):
    mock_ws = AsyncMock()
    await manager.disconnect("no-such-user", mock_ws)
    # should not raise


@pytest.mark.asyncio
async def test_send_to_user(manager):
    mock_ws = AsyncMock()
    mock_ws.client_state = "CONNECTED"  # string comparison won't match WebSocketState enum

    from starlette.websockets import WebSocketState
    mock_ws.client_state = WebSocketState.CONNECTED

    await manager.connect("user-1", mock_ws)
    count = await manager.send_to_user("user-1", {"type": "test"})
    assert count == 1
    mock_ws.send_json.assert_called_once_with({"type": "test"})


@pytest.mark.asyncio
async def test_send_to_user_no_connections(manager):
    count = await manager.send_to_user("no-such-user", {"type": "test"})
    assert count == 0


@pytest.mark.asyncio
async def test_send_to_user_disconnected(manager):
    from starlette.websockets import WebSocketState

    mock_ws = AsyncMock()
    mock_ws.client_state = WebSocketState.DISCONNECTED

    await manager.connect("user-1", mock_ws)
    count = await manager.send_to_user("user-1", {"type": "test"})
    assert count == 0
    mock_ws.send_json.assert_not_called()


@pytest.mark.asyncio
async def test_send_to_user_exception_handled(manager):
    from starlette.websockets import WebSocketState

    mock_ws = AsyncMock()
    mock_ws.client_state = WebSocketState.CONNECTED
    mock_ws.send_json.side_effect = Exception("connection lost")

    await manager.connect("user-1", mock_ws)
    count = await manager.send_to_user("user-1", {"type": "test"})
    assert count == 0  # exception caught, 0 success


@pytest.mark.asyncio
async def test_broadcast(manager):
    from starlette.websockets import WebSocketState

    ws1, ws2 = AsyncMock(), AsyncMock()
    ws1.client_state = WebSocketState.CONNECTED
    ws2.client_state = WebSocketState.CONNECTED

    await manager.connect("user-1", ws1)
    await manager.connect("user-2", ws2)

    count = await manager.broadcast({"type": "alert"})
    assert count == 2
    ws1.send_json.assert_called_once_with({"type": "alert"})
    ws2.send_json.assert_called_once_with({"type": "alert"})


@pytest.mark.asyncio
async def test_broadcast_empty(manager):
    count = await manager.broadcast({"type": "test"})
    assert count == 0


@pytest.mark.asyncio
async def test_send_new_literature_alert(manager):
    from starlette.websockets import WebSocketState

    mock_ws = AsyncMock()
    mock_ws.client_state = WebSocketState.CONNECTED
    await manager.connect("user-1", mock_ws)

    count = await manager.send_new_literature_alert("Novel Therapy", 12345, "Nature")
    assert count == 1
    sent_msg = mock_ws.send_json.call_args[0][0]
    assert sent_msg["type"] == "new_literature"
    assert sent_msg["pmid"] == 12345
    assert "Novel Therapy" in sent_msg["title"]
    assert "Nature" in sent_msg["journal"]


def test_online_count(manager):
    assert manager.online_count == 0

    import asyncio
    async def add():
        await manager.connect("user-1", AsyncMock())
        await manager.connect("user-1", AsyncMock())
        await manager.connect("user-2", AsyncMock())

    asyncio.run(add())
    assert manager.online_count == 3


def test_online_users(manager):
    assert manager.online_users == 0

    import asyncio
    async def add():
        await manager.connect("user-1", AsyncMock())
        await manager.connect("user-2", AsyncMock())

    asyncio.run(add())
    assert manager.online_users == 2


def test_ws_manager_singleton():
    from app.core.websocket import ws_manager, ConnectionManager
    assert isinstance(ws_manager, ConnectionManager)
