"""Tests for lock socket events (lock:update)."""

import time

import pytest
import requests
import socketio


@pytest.fixture
def authenticated_session(server, connect_room):
    """Create and join a room with persistent socket connection.

    Uses connect_room which provides a properly connected and room-joined socket.
    Returns server, room, session_id, auth_headers, sio (from conn), user_name.
    """
    user_name = "test-user"
    conn = connect_room("test-lock-socket", user=user_name)

    yield server, conn.room_id, conn.session_id, conn.headers, conn.sio, user_name
    # Cleanup handled by connect_room fixture


def test_lock_acquire_emits_lock_update(authenticated_session):
    """Test that acquiring a lock emits lock:update event with action=acquired."""
    server, room, session_id, auth_headers, sio, user_name = authenticated_session

    # Track received events
    received_events = []

    def on_lock_update(data):
        received_events.append(data)

    sio.on("lock:update", on_lock_update)

    # Acquire lock
    response = requests.post(
        f"{server}/api/rooms/{room}/locks/trajectory:meta/acquire",
        json={"msg": "Creating test geometry"},
        headers={**auth_headers, "X-Session-ID": session_id},
    )
    assert response.status_code == 200
    assert response.json()["success"] is True

    # Wait for socket event
    time.sleep(0.5)

    # Verify lock:update event was received
    assert len(received_events) == 1
    event = received_events[0]
    assert event["roomId"] == room
    assert event["target"] == "trajectory:meta"
    assert event["action"] == "acquired"
    assert event["holder"] == user_name
    assert event["message"] == "Creating test geometry"
    assert event["timestamp"] is not None
    assert event["sessionId"] == session_id


def test_idempotent_acquire_emits_refresh_event(authenticated_session):
    """Test that re-acquiring a lock (idempotent) emits lock:update with action=refreshed."""
    server, room, session_id, auth_headers, sio, user_name = authenticated_session

    # Track received events
    received_events = []

    def on_lock_update(data):
        received_events.append(data)

    sio.on("lock:update", on_lock_update)

    # Acquire lock first
    response = requests.post(
        f"{server}/api/rooms/{room}/locks/trajectory:meta/acquire",
        json={"msg": "Initial message"},
        headers={**auth_headers, "X-Session-ID": session_id},
    )
    assert response.status_code == 200
    lock_token = response.json()["lockToken"]
    assert response.json()["refreshed"] is False  # First acquire

    # Wait for acquire event, then clear it
    time.sleep(0.5)
    received_events.clear()

    # Re-acquire lock (idempotent) with new message
    response = requests.post(
        f"{server}/api/rooms/{room}/locks/trajectory:meta/acquire",
        json={"msg": "Updated message"},
        headers={**auth_headers, "X-Session-ID": session_id},
    )
    assert response.status_code == 200
    assert response.json()["success"] is True
    assert response.json()["refreshed"] is True  # Idempotent re-acquire
    assert response.json()["lockToken"] == lock_token  # Same token returned

    # Wait for socket event
    time.sleep(0.5)

    # Verify lock:update event was received with action=refreshed
    assert len(received_events) == 1
    event = received_events[0]
    assert event["roomId"] == room
    assert event["target"] == "trajectory:meta"
    assert event["action"] == "refreshed"
    assert event["holder"] == user_name
    assert event["message"] == "Updated message"
    assert event["timestamp"] is not None
    assert event["sessionId"] == session_id


def test_lock_release_emits_lock_update(authenticated_session):
    """Test that releasing a lock emits lock:update event with action=released."""
    server, room, session_id, auth_headers, sio, _ = authenticated_session

    # Track received events
    received_events = []

    def on_lock_update(data):
        received_events.append(data)

    sio.on("lock:update", on_lock_update)

    # Acquire lock first
    response = requests.post(
        f"{server}/api/rooms/{room}/locks/trajectory:meta/acquire",
        json={"msg": "Test lock"},
        headers={**auth_headers, "X-Session-ID": session_id},
    )
    assert response.status_code == 200
    lock_token = response.json()["lockToken"]

    # Wait for acquire event, then clear it
    time.sleep(0.5)
    received_events.clear()

    # Release lock
    response = requests.post(
        f"{server}/api/rooms/{room}/locks/trajectory:meta/release",
        json={"lockToken": lock_token},
        headers={**auth_headers, "X-Session-ID": session_id},
    )
    assert response.status_code == 200
    assert response.json()["success"] is True

    # Wait for socket event
    time.sleep(0.5)

    # Verify lock:update event was received
    assert len(received_events) == 1
    event = received_events[0]
    assert event["roomId"] == room
    assert event["target"] == "trajectory:meta"
    assert event["action"] == "released"
    assert event["holder"] is None
    assert event["message"] is None
    assert event["timestamp"] is None
    assert event["sessionId"] == session_id


def test_lock_acquire_without_message(authenticated_session):
    """Test that acquiring a lock without a message still emits lock:update."""
    server, room, session_id, auth_headers, sio, user_name = authenticated_session

    # Track received events
    received_events = []

    def on_lock_update(data):
        received_events.append(data)

    sio.on("lock:update", on_lock_update)

    # Acquire lock without message
    response = requests.post(
        f"{server}/api/rooms/{room}/locks/trajectory:meta/acquire",
        json={},
        headers={**auth_headers, "X-Session-ID": session_id},
    )
    assert response.status_code == 200
    assert response.json()["success"] is True

    # Wait for socket event
    time.sleep(0.5)

    # Verify lock:update event was received
    assert len(received_events) == 1
    event = received_events[0]
    assert event["roomId"] == room
    assert event["target"] == "trajectory:meta"
    assert event["action"] == "acquired"
    assert event["holder"] == user_name
    assert event["message"] is None
    assert event["timestamp"] is None


def test_lock_events_for_different_targets(authenticated_session):
    """Test that lock events work for different lock targets."""
    server, room, session_id, auth_headers, sio, user_name = authenticated_session

    # Track received events
    received_events = []

    def on_lock_update(data):
        received_events.append(data)

    sio.on("lock:update", on_lock_update)

    # Acquire lock for custom target
    response = requests.post(
        f"{server}/api/rooms/{room}/locks/geometry:selection/acquire",
        json={"msg": "Selecting geometry"},
        headers={**auth_headers, "X-Session-ID": session_id},
    )
    assert response.status_code == 200

    # Wait for socket event
    time.sleep(0.5)

    # Verify lock:update event was received for custom target
    assert len(received_events) == 1
    event = received_events[0]
    assert event["target"] == "geometry:selection"
    assert event["action"] == "acquired"
    assert event["holder"] == user_name


def test_multiple_clients_receive_lock_events(
    server, get_jwt_auth_headers, create_and_join_room
):
    """Test that multiple clients in the same room receive lock:update events."""
    room = "test-multi-client"
    user1_name = "user1"
    user2_name = "user2"

    # User 1 creates and joins room
    user1_headers = get_jwt_auth_headers(server, user1_name)
    user1_session = create_and_join_room(server, room, user1_headers)

    # User 2 joins existing room (listener) via socket
    user2_headers = get_jwt_auth_headers(server, user2_name)
    user2_events = []

    def on_lock_update(data):
        user2_events.append(data)

    sio_client = socketio.Client()
    sio_client.on("lock:update", on_lock_update)
    sio_client.connect(
        server, auth={"token": user2_headers["Authorization"].split(" ")[1]}
    )
    sio_client.call("room:join", {"roomId": room, "clientType": "frontend"})

    # Give socket time to connect
    time.sleep(0.5)

    # User 1 acquires lock
    response = requests.post(
        f"{server}/api/rooms/{room}/locks/trajectory:meta/acquire",
        json={"msg": "User 1 lock"},
        headers={**user1_headers, "X-Session-ID": user1_session},
    )
    assert response.status_code == 200

    # Wait for socket event
    time.sleep(0.5)

    # Verify user 2 received the lock event from user 1
    assert len(user2_events) == 1
    event = user2_events[0]
    assert event["holder"] == user1_name
    assert event["message"] == "User 1 lock"
    assert event["sessionId"] == user1_session  # Should include user1's sessionId

    sio_client.disconnect()


def test_lock_event_includes_session_id_for_filtering(authenticated_session):
    """Test that lock:update events include sessionId so clients can filter their own events."""
    server, room, session_id, auth_headers, sio, user_name = authenticated_session

    # Track all received events
    received_events = []

    def on_lock_update(data):
        received_events.append(data)

    sio.on("lock:update", on_lock_update)

    # Acquire lock
    response = requests.post(
        f"{server}/api/rooms/{room}/locks/step/acquire",
        json={"msg": "Testing step lock"},
        headers={**auth_headers, "X-Session-ID": session_id},
    )
    assert response.status_code == 200
    lock_token = response.json()["lockToken"]

    # Wait for socket event
    time.sleep(0.5)

    # Verify event was received with sessionId
    assert len(received_events) == 1
    acquire_event = received_events[0]
    assert acquire_event["target"] == "step"
    assert acquire_event["action"] == "acquired"
    assert acquire_event["sessionId"] == session_id
    assert acquire_event["holder"] == user_name

    # Client can now filter: if event.sessionId == mySessionId, ignore it
    my_session_id = session_id
    should_ignore = acquire_event["sessionId"] == my_session_id
    assert should_ignore is True  # This is our own event, should be ignored

    # Re-acquire the lock (idempotent) with a message
    received_events.clear()
    response = requests.post(
        f"{server}/api/rooms/{room}/locks/step/acquire",
        json={"msg": "Still updating"},
        headers={**auth_headers, "X-Session-ID": session_id},
    )
    assert response.status_code == 200
    assert response.json()["refreshed"] is True
    time.sleep(0.5)

    # Verify refresh event also includes sessionId
    assert len(received_events) == 1
    refresh_event = received_events[0]
    assert refresh_event["action"] == "refreshed"
    assert refresh_event["sessionId"] == session_id

    # Release the lock
    received_events.clear()
    response = requests.post(
        f"{server}/api/rooms/{room}/locks/step/release",
        json={"lockToken": lock_token},
        headers={**auth_headers, "X-Session-ID": session_id},
    )
    assert response.status_code == 200
    time.sleep(0.5)

    # Verify release event includes sessionId
    assert len(received_events) == 1
    release_event = received_events[0]
    assert release_event["action"] == "released"
    assert release_event["sessionId"] == session_id
