"""Test _ws_ref lifecycle: null-check, disconnect cleanup, reconnect."""
import asyncio
import json
import os
import pytest

# Minimal stubs so we can import ws_client without ROS2/rclpy
import sys
from unittest.mock import MagicMock, AsyncMock, patch

# Set required env vars before importing bridge modules
os.environ.setdefault("DEVICE_ID", "test-device")
os.environ.setdefault("GATEWAY_URL", "ws://localhost:8002")
os.environ.setdefault("API_KEY", "test-key")

# Stub heavy deps before importing bridge modules
sys.modules.setdefault("rclpy", MagicMock())
sys.modules.setdefault("rclpy.node", MagicMock())
sys.modules.setdefault("rclpy.qos", MagicMock())

import ai_teammate_ros2_bridge.config as _cfg
from ai_teammate_ros2_bridge.ws_client import _send_service_request, _handle_service_response

# Also test standalone device_bridge version
from ai_teammate_ros2_bridge import device_bridge as _db


# ── ws_client tests ──

@pytest.mark.asyncio
async def test_send_when_ws_ref_is_none():
    """_ws_ref가 None이면 즉시 에러 반환해야 함."""
    _cfg._ws_ref = None
    result = await _send_service_request({"action": "MAP_UPLOAD"})
    assert result["status"] == "error"
    assert "not connected" in result["message"]


@pytest.mark.asyncio
async def test_send_success():
    """정상 WS 커넥션이면 send 후 응답 대기."""
    mock_ws = AsyncMock()
    _cfg._ws_ref = mock_ws

    async def fake_response():
        await asyncio.sleep(0.01)
        # Extract request_id from the send call
        call_args = mock_ws.send.call_args
        sent = json.loads(call_args[0][0])
        _handle_service_response({
            "request_id": sent["data"]["request_id"],
            "status": "ok",
            "url": "https://example.com/map.pgm",
        })

    task = asyncio.create_task(fake_response())
    result = await _send_service_request({"action": "MAP_UPLOAD"}, timeout=2.0)
    await task

    assert result["status"] == "ok"
    mock_ws.send.assert_called_once()
    _cfg._ws_ref = None


@pytest.mark.asyncio
async def test_send_closed_ws_returns_error():
    """WS가 닫혀서 send()가 예외를 던지면 에러 반환."""
    mock_ws = AsyncMock()
    mock_ws.send.side_effect = Exception("connection closed")
    _cfg._ws_ref = mock_ws

    result = await _send_service_request({"action": "MAP_UPLOAD"})
    assert result["status"] == "error"
    assert "connection closed" in result["message"]
    _cfg._ws_ref = None


@pytest.mark.asyncio
async def test_disconnect_clears_ws_ref_and_futures():
    """disconnect 시 _ws_ref를 None으로, pending futures를 에러로 resolve."""
    from ai_teammate_ros2_bridge.ws_client import _service_futures

    # Simulate a pending future
    loop = asyncio.get_event_loop()
    fut = loop.create_future()
    _service_futures["test123"] = fut

    _cfg._ws_ref = AsyncMock()

    # Simulate disconnect cleanup (same logic as except block)
    _cfg._ws_ref = None
    for rid, f in list(_service_futures.items()):
        if not f.done():
            f.set_result({"status": "error", "message": "WebSocket disconnected"})
    _service_futures.clear()

    assert _cfg._ws_ref is None
    assert fut.done()
    assert fut.result()["status"] == "error"
    assert len(_service_futures) == 0


# ── device_bridge standalone tests ──

@pytest.mark.asyncio
async def test_db_send_when_ws_ref_is_none():
    """device_bridge: _ws_ref가 None이면 즉시 에러."""
    _db._ws_ref = None
    result = await _db._send_service_request({"action": "MAP_UPLOAD"})
    assert result["status"] == "error"
    assert "not connected" in result["message"]


@pytest.mark.asyncio
async def test_db_send_closed_ws():
    """device_bridge: 닫힌 WS에서 에러 반환."""
    mock_ws = AsyncMock()
    mock_ws.send.side_effect = Exception("closed")
    _db._ws_ref = mock_ws

    result = await _db._send_service_request({"action": "MAP_UPLOAD"})
    assert result["status"] == "error"
    _db._ws_ref = None
