import pytest
from fastapi import status
from unittest.mock import AsyncMock
from app.services.presence import active_sessions, add_active_session, remove_active_session, connected_agents, add_agent_connection, remove_agent_connection
from app.services.video_signaling import active_calls, get_call_state, set_call_state, handle_visitor_signaling, handle_agent_signaling, terminate_video_call
from app.database import get_database
from app.repositories.session_repository import create_session

@pytest.mark.anyio
async def test_visitor_initiates_call_routed_to_assigned_agent(client):
    db = get_database()
    sess = await create_session(db, site_id="site_demo_123")
    sid = sess["sessionId"]

    # 1. Setup visitor and agent mock sockets
    mock_visitor_ws = AsyncMock()
    mock_agent_ws = AsyncMock()

    add_active_session(
        session_id=sid,
        site_id="site_demo_123",
        websocket=mock_visitor_ws,
        status_val="IN_CHAT",
        assigned_agent_id="agent_alpha"
    )
    add_agent_connection(agent_id="agent_alpha", websocket=mock_agent_ws)

    # 2. Visitor sends call request with WebRTC offer
    offer_sdp = {"type": "offer", "sdp": "v=0\r\no=visitor 12345 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n"}
    await handle_visitor_signaling(sid, {
        "type": "video_call_requested",
        "offer": offer_sdp
    })

    # 3. Verify call state is updated
    assert get_call_state(sid) == "CALL_REQUESTED"

    # 4. Verify assigned agent received the call request
    mock_agent_ws.send_text.assert_called_once()
    sent_to_agent = mock_agent_ws.send_text.call_args[0][0]
    import json
    parsed = json.loads(sent_to_agent)
    assert parsed["type"] == "video_call_requested"
    assert parsed["sessionId"] == sid
    assert parsed["offer"]["sdp"] == offer_sdp["sdp"]

    # Cleanup
    remove_active_session(sid)
    remove_agent_connection("agent_alpha")
    active_calls.pop(sid, None)

@pytest.mark.anyio
async def test_agent_accepts_call_routed_to_visitor(client):
    db = get_database()
    sess = await create_session(db, site_id="site_demo_123")
    sid = sess["sessionId"]

    mock_visitor_ws = AsyncMock()
    mock_agent_ws = AsyncMock()

    add_active_session(
        session_id=sid,
        site_id="site_demo_123",
        websocket=mock_visitor_ws,
        status_val="IN_CHAT",
        assigned_agent_id="agent_alpha"
    )
    add_agent_connection(agent_id="agent_alpha", websocket=mock_agent_ws)
    set_call_state(sid, "CALL_REQUESTED", "agent_alpha")

    # Agent sends WebRTC answer
    answer_sdp = {"type": "answer", "sdp": "v=0\r\no=agent 54321 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n"}
    await handle_agent_signaling("agent_alpha", {
        "type": "webrtc_answer",
        "sessionId": sid,
        "answer": answer_sdp
    })

    # Verify state and visitor notification
    assert get_call_state(sid) == "ACCEPTED"
    mock_visitor_ws.send_text.assert_called_once()
    import json
    parsed = json.loads(mock_visitor_ws.send_text.call_args[0][0])
    assert parsed["type"] == "webrtc_answer"
    assert parsed["sessionId"] == sid
    assert parsed["answer"]["sdp"] == answer_sdp["sdp"]

    # Cleanup
    remove_active_session(sid)
    remove_agent_connection("agent_alpha")
    active_calls.pop(sid, None)

@pytest.mark.anyio
async def test_bidirectional_ice_candidate_routing(client):
    db = get_database()
    sess = await create_session(db, site_id="site_demo_123")
    sid = sess["sessionId"]

    mock_visitor_ws = AsyncMock()
    mock_agent_ws = AsyncMock()

    add_active_session(
        session_id=sid,
        site_id="site_demo_123",
        websocket=mock_visitor_ws,
        status_val="IN_CHAT",
        assigned_agent_id="agent_alpha"
    )
    add_agent_connection(agent_id="agent_alpha", websocket=mock_agent_ws)

    # 1. Visitor -> Agent ICE candidate
    candidate_visitor = {"candidate": "candidate:1 1 UDP 2122260223 192.168.1.100 54321 typ host", "sdpMid": "0"}
    await handle_visitor_signaling(sid, {
        "type": "webrtc_ice_candidate",
        "candidate": candidate_visitor
    })
    mock_agent_ws.send_text.assert_called_once()
    import json
    parsed_agent = json.loads(mock_agent_ws.send_text.call_args[0][0])
    assert parsed_agent["type"] == "webrtc_ice_candidate"
    assert parsed_agent["candidate"]["sdpMid"] == "0"

    # 2. Agent -> Visitor ICE candidate
    candidate_agent = {"candidate": "candidate:2 1 UDP 2122260223 192.168.1.200 65432 typ host", "sdpMid": "0"}
    await handle_agent_signaling("agent_alpha", {
        "type": "webrtc_ice_candidate",
        "sessionId": sid,
        "candidate": candidate_agent
    })
    mock_visitor_ws.send_text.assert_called_once()
    parsed_visitor = json.loads(mock_visitor_ws.send_text.call_args[0][0])
    assert parsed_visitor["type"] == "webrtc_ice_candidate"
    assert parsed_visitor["candidate"]["sdpMid"] == "0"

    remove_active_session(sid)
    remove_agent_connection("agent_alpha")

@pytest.mark.anyio
async def test_agent_rejects_call(client):
    db = get_database()
    sess = await create_session(db, site_id="site_demo_123")
    sid = sess["sessionId"]

    mock_visitor_ws = AsyncMock()
    add_active_session(sid, "site_demo_123", mock_visitor_ws, status_val="IN_CHAT", assigned_agent_id="agent_alpha")
    set_call_state(sid, "CALL_REQUESTED", "agent_alpha")

    await handle_agent_signaling("agent_alpha", {
        "type": "video_call_rejected",
        "sessionId": sid,
        "reason": "Agent is currently busy"
    })

    assert get_call_state(sid) == "IDLE"
    mock_visitor_ws.send_text.assert_called_once()
    import json
    parsed = json.loads(mock_visitor_ws.send_text.call_args[0][0])
    assert parsed["type"] == "video_call_rejected"
    assert parsed["reason"] == "Agent is currently busy"

    remove_active_session(sid)

@pytest.mark.anyio
async def test_unassigned_agent_cannot_signal(client):
    db = get_database()
    sess = await create_session(db, site_id="site_demo_123")
    sid = sess["sessionId"]

    mock_visitor_ws = AsyncMock()
    add_active_session(sid, "site_demo_123", mock_visitor_ws, status_val="IN_CHAT", assigned_agent_id="agent_alpha")

    # Intruder agent tries to send WebRTC answer
    await handle_agent_signaling("agent_rogue", {
        "type": "webrtc_answer",
        "sessionId": sid,
        "answer": {"type": "answer", "sdp": "fake"}
    })

    # Visitor socket should NOT receive anything
    mock_visitor_ws.send_text.assert_not_called()
    assert get_call_state(sid) == "IDLE"

    remove_active_session(sid)

@pytest.mark.anyio
async def test_visitor_call_request_fails_if_not_in_chat(client):
    db = get_database()
    sess = await create_session(db, site_id="site_demo_123")
    sid = sess["sessionId"]

    mock_visitor_ws = AsyncMock()
    # Session is still RINGING (not yet accepted by an agent)
    add_active_session(sid, "site_demo_123", mock_visitor_ws, status_val="RINGING")

    await handle_visitor_signaling(sid, {
        "type": "video_call_requested",
        "offer": {"type": "offer", "sdp": "fake"}
    })

    # Visitor receives rejection
    mock_visitor_ws.send_text.assert_called_once()
    import json
    parsed = json.loads(mock_visitor_ws.send_text.call_args[0][0])
    assert parsed["type"] == "video_call_rejected"

    remove_active_session(sid)

@pytest.mark.anyio
async def test_chat_end_terminates_active_video_call(client):
    db = get_database()
    sess = await create_session(db, site_id="site_demo_123")
    sid = sess["sessionId"]

    mock_visitor_ws = AsyncMock()
    mock_agent_ws = AsyncMock()

    add_active_session(sid, "site_demo_123", mock_visitor_ws, status_val="IN_CHAT", assigned_agent_id="agent_alpha")
    add_agent_connection("agent_alpha", mock_agent_ws)
    set_call_state(sid, "CONNECTED", "agent_alpha")

    # End chat via API
    res = client.post("/api/v1/handoff/end", json={"sessionId": sid, "endedBy": "agent"})
    assert res.status_code == status.HTTP_200_OK

    # Video call should be terminated
    assert get_call_state(sid) == "IDLE"

    remove_active_session(sid)
    remove_agent_connection("agent_alpha")
