import uuid
import pytest
from app.database import get_database
from app.repositories.session_repository import create_session, get_session_by_id, update_session_status
from app.services.presence import (
    add_agent_connection,
    remove_agent_connection,
    get_agent_connection,
    handle_agent_disconnect,
    active_sessions,
    connected_agents
)

class MockWebSocket:
    def __init__(self, client_id: str = "mock"):
        self.client_id = client_id
        self.sent_messages = []
        self.is_closed = False

    async def send_text(self, text: str):
        if self.is_closed:
            raise RuntimeError("WebSocket is closed")
        self.sent_messages.append(text)

    async def close(self, code: int = 1000, reason: str = ""):
        self.is_closed = True

@pytest.fixture(autouse=True)
def reset_presence_state():
    active_sessions.clear()
    connected_agents.clear()
    yield
    active_sessions.clear()
    connected_agents.clear()

def create_test_session(client):
    domain = f"shop-{uuid.uuid4().hex[:6]}.com"
    reg_resp = client.post("/api/v1/tenant/register", json={"domain": domain, "name": "Test Shop"})
    site_id = reg_resp.json()["siteId"]
    headers = {"Origin": f"http://{domain}"}
    res = client.post("/api/v1/session/init", json={"siteId": site_id}, headers=headers)
    assert res.status_code == 201
    session_id = res.json()["sessionId"]
    active_sessions[session_id] = {
        "sessionId": session_id,
        "siteId": site_id,
        "status": "RINGING",
        "user": None,
        "ipAddress": "127.0.0.1",
        "country": "Unknown",
        "connectedAt": "2026-08-18T12:00:00Z",
        "websocket": MockWebSocket("visitor")
    }
    return session_id, site_id

def test_first_agent_successfully_claims_session(client):
    session_id, _ = create_test_session(client)

    # Register Agent 1 in presence pool
    ws1 = MockWebSocket("agent_1")
    add_agent_connection("agent_1", ws1)

    # Agent 1 claims session
    accept_res = client.post("/api/v1/handoff/accept", json={"sessionId": session_id, "agentId": "agent_1"})
    assert accept_res.status_code == 200
    data = accept_res.json()
    assert data["sessionId"] == session_id
    assert data["status"] == "IN_CHAT"
    assert data["assignedAgentId"] == "agent_1"

    # Verify agent 1 status is set to BUSY
    assert get_agent_connection("agent_1")["status"] == "BUSY"

def test_second_agent_claim_attempt_returns_409_conflict(client):
    session_id, _ = create_test_session(client)

    ws1 = MockWebSocket("agent_1")
    ws2 = MockWebSocket("agent_2")
    add_agent_connection("agent_1", ws1)
    add_agent_connection("agent_2", ws2)

    # Agent 1 claims session first
    accept1 = client.post("/api/v1/handoff/accept", json={"sessionId": session_id, "agentId": "agent_1"})
    assert accept1.status_code == 200

    # Agent 2 attempts to claim the same session
    accept2 = client.post("/api/v1/handoff/accept", json={"sessionId": session_id, "agentId": "agent_2"})
    assert accept2.status_code == 409
    assert accept2.json()["detail"] == "Chat session already claimed by another agent"

def test_assigned_agent_and_assigned_at_persisted(client):
    session_id, _ = create_test_session(client)

    ws1 = MockWebSocket("agent_1")
    add_agent_connection("agent_1", ws1)

    client.post("/api/v1/handoff/accept", json={"sessionId": session_id, "agentId": "agent_1"})

    # Check active-sessions endpoint
    active_res = client.get("/api/v1/admin/active-sessions")
    assert active_res.status_code == 200
    sessions = active_res.json()
    matched = next((s for s in sessions if s["sessionId"] == session_id), None)
    assert matched is not None
    assert matched["assignedAgentId"] == "agent_1"
    assert matched["assignedAt"] is not None

def test_agent_ownership_validation_on_message(client):
    session_id, _ = create_test_session(client)

    ws1 = MockWebSocket("agent_1")
    ws2 = MockWebSocket("agent_2")
    add_agent_connection("agent_1", ws1)
    add_agent_connection("agent_2", ws2)

    # Agent 1 claims session
    client.post("/api/v1/handoff/accept", json={"sessionId": session_id, "agentId": "agent_1"})

    # Agent 2 attempts to send a message to Agent 1's chat
    msg2 = client.post("/api/v1/handoff/message", json={
        "sessionId": session_id,
        "message": "Hello from intruder",
        "agentId": "agent_2"
    })
    assert msg2.status_code == 403
    assert msg2.json()["detail"] == "You do not own this chat session"

    # Agent 1 sends message successfully
    msg1 = client.post("/api/v1/handoff/message", json={
        "sessionId": session_id,
        "message": "Hello from owner",
        "agentId": "agent_1"
    })
    assert msg1.status_code == 200
    assert msg1.json()["reply"] == "Hello from owner"

def test_visitor_message_includes_assigned_agent_id(client):
    session_id, _ = create_test_session(client)

    ws1 = MockWebSocket("agent_1")
    add_agent_connection("agent_1", ws1)

    client.post("/api/v1/handoff/accept", json={"sessionId": session_id, "agentId": "agent_1"})

    # Visitor sends a message
    user_msg_res = client.post("/api/v1/message", json={"sessionId": session_id, "message": "Help me please"})
    assert user_msg_res.status_code == 200
    assert user_msg_res.json()["reply"] == "Message delivered to agent."

    # Check WS message sent to agent 1
    assert len(ws1.sent_messages) > 0
    import json
    last_msg = json.loads(ws1.sent_messages[-1])
    assert last_msg["type"] == "user_message"
    assert last_msg["assignedAgentId"] == "agent_1"

@pytest.mark.anyio
async def test_agent_disconnect_causes_requeue_to_ringing():
    db = get_database()
    session = await create_session(db, site_id="site_demo_123")
    session_id = session["sessionId"]

    ws1 = MockWebSocket("agent_alpha")
    ws2 = MockWebSocket("agent_beta")
    add_agent_connection("agent_alpha", ws1)
    add_agent_connection("agent_beta", ws2)

    # Claim session for agent_alpha
    from app.repositories.session_repository import claim_session_atomically
    await claim_session_atomically(db, session_id, agent_id="agent_alpha")

    active_sessions[session_id] = {
        "sessionId": session_id,
        "siteId": "site_demo_123",
        "status": "IN_CHAT",
        "assignedAgentId": "agent_alpha",
        "assignedAt": "2026-08-18T12:00:00Z",
        "websocket": MockWebSocket("visitor")
    }

    # Simulate agent_alpha disconnect
    remove_agent_connection("agent_alpha", ws1)
    await handle_agent_disconnect(db, "agent_alpha")

    # Verify session in memory is requeued to RINGING
    assert active_sessions[session_id]["status"] == "RINGING"
    assert active_sessions[session_id].get("assignedAgentId") is None
    assert active_sessions[session_id].get("assignedAt") is None

    # Verify DB state
    fetched = await get_session_by_id(db, session_id)
    assert fetched["status"] == "RINGING"
    assert fetched.get("assignedAgentId") is None

    # Verify agent_beta received visitor_requeued notification
    assert len(ws2.sent_messages) > 0
    import json
    requeue_event = json.loads(ws2.sent_messages[-1])
    assert requeue_event["type"] == "visitor_requeued"
    assert requeue_event["sessionId"] == session_id

    # Now agent_beta can claim the requeued session
    claimed_beta = await claim_session_atomically(db, session_id, agent_id="agent_beta")
    assert claimed_beta is not None
    assert claimed_beta["assignedAgentId"] == "agent_beta"
    assert claimed_beta["status"] == "IN_CHAT"

@pytest.mark.anyio
async def test_ending_ended_session_does_not_requeue(client):
    db = get_database()
    session = await create_session(db, site_id="site_demo_123")
    session_id = session["sessionId"]

    ws1 = MockWebSocket("agent_1")
    add_agent_connection("agent_1", ws1)

    # Claim and then END session
    from app.repositories.session_repository import claim_session_atomically
    await claim_session_atomically(db, session_id, agent_id="agent_1")
    await update_session_status(db, session_id, "ENDED")

    active_sessions[session_id] = {
        "sessionId": session_id,
        "siteId": "site_demo_123",
        "status": "ENDED",
        "assignedAgentId": "agent_1"
    }

    # Simulate agent_1 disconnect
    remove_agent_connection("agent_1", ws1)
    await handle_agent_disconnect(db, "agent_1")

    # Verify session remains ENDED
    assert active_sessions[session_id]["status"] == "ENDED"
    fetched = await get_session_by_id(db, session_id)
    assert fetched["status"] == "ENDED"

def test_chat_claim_realtime_broadcast_to_other_agents(client):
    session_id, site_id = create_test_session(client)

    # Register Agent 1 and Agent 2 in presence pool
    ws1 = MockWebSocket("agent_1")
    ws2 = MockWebSocket("agent_2")
    add_agent_connection("agent_1", ws1)
    add_agent_connection("agent_2", ws2)

    # Agent 1 accepts the chat session
    accept_res = client.post("/api/v1/handoff/accept", json={"sessionId": session_id, "agentId": "agent_1"})
    assert accept_res.status_code == 200

    # Verify Agent 2 received the real-time chat_claimed broadcast
    import json
    ws2_messages = [json.loads(m) for m in ws2.sent_messages]
    claimed_events = [m for m in ws2_messages if m.get("type") == "chat_claimed"]
    assert len(claimed_events) > 0

    event = claimed_events[0]
    assert event["sessionId"] == session_id
    assert event["assignedAgentId"] == "agent_1"
    assert event["status"] == "IN_CHAT"
