"""Tests for spectator broadcasting in IF mode."""

from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib.if_session import IFResponse
from mudlib.player import Player, players
from mudlib.zone import Zone


@pytest.fixture
def mock_writer():
    writer = MagicMock()
    writer.write = MagicMock()
    writer.drain = AsyncMock()
    return writer


@pytest.fixture
def mock_reader():
    return MagicMock()


@pytest.fixture(autouse=True)
def clear_players():
    """Clear players registry before and after each test."""
    players.clear()
    yield
    players.clear()


@pytest.fixture
def test_zone():
    """Create a test zone for spatial queries."""
    # Create a small test zone with passable terrain
    terrain = [["."] * 20 for _ in range(20)]
    return Zone(name="test", width=20, height=20, terrain=terrain, toroidal=False)


@pytest.fixture
def player_a(test_zone):
    """Player A at (5, 5) who will be playing IF."""
    writer = MagicMock()
    writer.write = MagicMock()
    writer.drain = AsyncMock()
    reader = MagicMock()
    return Player(
        name="PlayerA", location=test_zone, x=5, y=5, reader=reader, writer=writer
    )


@pytest.fixture
def player_b(test_zone):
    """Player B at (5, 5) who will be spectating."""
    writer = MagicMock()
    writer.write = MagicMock()
    writer.drain = AsyncMock()
    reader = MagicMock()
    return Player(
        name="PlayerB", location=test_zone, x=5, y=5, reader=reader, writer=writer
    )


@pytest.fixture
def player_c(test_zone):
    """Player C at different coords (10, 10)."""
    writer = MagicMock()
    writer.write = MagicMock()
    writer.drain = AsyncMock()
    reader = MagicMock()
    return Player(
        name="PlayerC", location=test_zone, x=10, y=10, reader=reader, writer=writer
    )


@pytest.mark.asyncio
async def test_spectator_sees_if_output(player_a, player_b):
    """Spectator at same location sees IF output with header and input."""
    # Register both players
    players[player_a.name] = player_a
    players[player_b.name] = player_b

    # Create mock IF session for player A
    mock_session = MagicMock()
    mock_session.handle_input = AsyncMock(
        return_value=IFResponse(
            output="Opening the small mailbox reveals a leaflet.", done=False
        )
    )
    player_a.if_session = mock_session
    player_a.mode_stack.append("if")

    # Import the broadcast function (will be created in implementation)
    from mudlib.if_session import broadcast_to_spectators

    # Player A sends input
    command = "open mailbox"
    response = await player_a.if_session.handle_input(command)
    await player_a.send(response.output)

    # Broadcast to spectators
    spectator_msg = f"[{player_a.name}'s terminal]\r\n> {command}\r\n{response.output}"
    await broadcast_to_spectators(player_a, spectator_msg)

    # Player B should have received the message
    assert player_b.writer.write.called
    calls = player_b.writer.write.call_args_list
    sent_text = "".join(call[0][0] for call in calls)
    assert "[PlayerA's terminal]" in sent_text
    assert "> open mailbox" in sent_text
    assert "Opening the small mailbox reveals a leaflet." in sent_text


@pytest.mark.asyncio
async def test_spectator_not_on_same_tile_sees_nothing(player_a, player_c):
    """Spectator at different location does not see IF output."""
    # Register both players (player_c is at different coords)
    players[player_a.name] = player_a
    players[player_c.name] = player_c

    # Create mock IF session for player A
    mock_session = MagicMock()
    mock_session.handle_input = AsyncMock(
        return_value=IFResponse(output="You open the door.", done=False)
    )
    player_a.if_session = mock_session
    player_a.mode_stack.append("if")

    from mudlib.if_session import broadcast_to_spectators

    # Player A sends input
    command = "open door"
    response = await player_a.if_session.handle_input(command)
    await player_a.send(response.output)

    # Broadcast to spectators
    spectator_msg = f"[{player_a.name}'s terminal]\r\n> {command}\r\n{response.output}"
    await broadcast_to_spectators(player_a, spectator_msg)

    # Player C should NOT have received anything
    assert not player_c.writer.write.called


@pytest.mark.asyncio
async def test_spectator_sees_game_start(player_a, player_b):
    """Spectator sees formatted intro text when player starts game."""
    # Register both players
    players[player_a.name] = player_a
    players[player_b.name] = player_b

    from mudlib.if_session import broadcast_to_spectators

    # Simulate game start with intro text
    intro = "ZORK I: The Great Underground Empire\nCopyright (c) 1981"
    spectator_msg = f"[{player_a.name}'s terminal]\r\n{intro}\r\n"

    await broadcast_to_spectators(player_a, spectator_msg)

    # Player B should see the intro
    assert player_b.writer.write.called
    calls = player_b.writer.write.call_args_list
    sent_text = "".join(call[0][0] for call in calls)
    assert "[PlayerA's terminal]" in sent_text
    assert "ZORK I: The Great Underground Empire" in sent_text


@pytest.mark.asyncio
async def test_broadcast_to_spectators_skips_self(player_a, player_b):
    """broadcast_to_spectators does not send to the playing player."""
    # Register both players
    players[player_a.name] = player_a
    players[player_b.name] = player_b

    from mudlib.if_session import broadcast_to_spectators

    message = "[PlayerA's terminal]\r\n> look\r\nYou see a room.\r\n"
    await broadcast_to_spectators(player_a, message)

    # Player A should NOT have received the message
    assert not player_a.writer.write.called
    # Player B should have received it
    assert player_b.writer.write.called


@pytest.mark.asyncio
async def test_multiple_spectators(player_a, player_b, test_zone):
    """Multiple spectators at same location all see IF output."""
    # Create a third player at same location
    writer_d = MagicMock()
    writer_d.write = MagicMock()
    writer_d.drain = AsyncMock()
    reader_d = MagicMock()
    player_d = Player(
        name="PlayerD", location=test_zone, x=5, y=5, reader=reader_d, writer=writer_d
    )

    # Register all players
    players[player_a.name] = player_a
    players[player_b.name] = player_b
    players[player_d.name] = player_d

    from mudlib.if_session import broadcast_to_spectators

    message = "[PlayerA's terminal]\r\n> inventory\r\nYou are empty-handed.\r\n"
    await broadcast_to_spectators(player_a, message)

    # Both spectators should see the message
    assert player_b.writer.write.called
    assert player_d.writer.write.called
    # Player A should not
    assert not player_a.writer.write.called
