"""Tests for portal enter command."""

from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib.commands import _registry
from mudlib.player import Player
from mudlib.portal import Portal
from mudlib.zone import Zone
from mudlib.zones import register_zone, zone_registry


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


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


@pytest.fixture
def test_zone():
    terrain = [["." for _ in range(10)] for _ in range(10)]
    return Zone(
        name="testzone",
        width=10,
        height=10,
        toroidal=True,
        terrain=terrain,
    )


@pytest.fixture
def target_zone():
    terrain = [["." for _ in range(10)] for _ in range(10)]
    return Zone(
        name="targetzone",
        width=10,
        height=10,
        toroidal=True,
        terrain=terrain,
    )


@pytest.fixture
def player(mock_reader, mock_writer, test_zone):
    p = Player(
        name="TestPlayer",
        x=5,
        y=5,
        reader=mock_reader,
        writer=mock_writer,
        location=test_zone,
    )
    return p


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


# --- cmd_enter ---


@pytest.mark.asyncio
async def test_enter_moves_to_target_zone(player, test_zone, target_zone):
    """enter portal moves player to target zone."""
    from mudlib.commands.portals import cmd_enter

    register_zone("targetzone", target_zone)
    Portal(
        name="shimmering doorway",
        aliases=["doorway"],
        location=test_zone,
        x=5,
        y=5,
        target_zone="targetzone",
        target_x=3,
        target_y=7,
    )

    await cmd_enter(player, "doorway")
    assert player.location is target_zone
    assert player.x == 3
    assert player.y == 7
    assert player in target_zone.contents
    assert player not in test_zone.contents


@pytest.mark.asyncio
async def test_enter_sends_departure_message(
    player, test_zone, target_zone, mock_writer
):
    """enter portal sends departure message to old zone."""
    from mudlib.commands.portals import cmd_enter

    register_zone("targetzone", target_zone)
    Portal(
        name="portal",
        location=test_zone,
        x=5,
        y=5,
        target_zone="targetzone",
        target_x=3,
        target_y=7,
    )
    other_player = Player(
        name="OtherPlayer",
        x=5,
        y=5,
        reader=MagicMock(),
        writer=MagicMock(write=MagicMock(), drain=AsyncMock()),
        location=test_zone,
    )

    await cmd_enter(player, "portal")

    # Check that other player received departure message
    other_writer = other_player.writer
    calls = [call[0][0] for call in other_writer.write.call_args_list]
    assert any("TestPlayer" in call and "enter" in call.lower() for call in calls)


@pytest.mark.asyncio
async def test_enter_sends_arrival_message(player, test_zone, target_zone):
    """enter portal sends arrival message to new zone."""
    from mudlib.commands.portals import cmd_enter

    register_zone("targetzone", target_zone)
    Portal(
        name="portal",
        location=test_zone,
        x=5,
        y=5,
        target_zone="targetzone",
        target_x=3,
        target_y=7,
    )
    other_player = Player(
        name="OtherPlayer",
        x=3,
        y=7,
        reader=MagicMock(),
        writer=MagicMock(write=MagicMock(), drain=AsyncMock()),
        location=target_zone,
    )

    await cmd_enter(player, "portal")

    # Check that other player in target zone received arrival message
    other_writer = other_player.writer
    calls = [call[0][0] for call in other_writer.write.call_args_list]
    assert any("TestPlayer" in call and "arrive" in call.lower() for call in calls)


@pytest.mark.asyncio
async def test_enter_shows_look_in_new_zone(
    player, test_zone, target_zone, mock_writer
):
    """enter portal triggers look command in new zone."""
    from mudlib.commands.portals import cmd_enter

    register_zone("targetzone", target_zone)
    Portal(
        name="portal",
        location=test_zone,
        x=5,
        y=5,
        target_zone="targetzone",
        target_x=3,
        target_y=7,
    )

    await cmd_enter(player, "portal")

    # Check that look output was sent (viewport grid should be in output)
    output = "".join([call[0][0] for call in mock_writer.write.call_args_list])
    # Should have the @ symbol for player position
    assert "@" in output


@pytest.mark.asyncio
async def test_enter_no_args(player, mock_writer):
    """enter with no arguments gives usage hint."""
    from mudlib.commands.portals import cmd_enter

    await cmd_enter(player, "")
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "enter what" in output.lower() or "usage" in output.lower()


@pytest.mark.asyncio
async def test_enter_portal_not_found(player, test_zone, mock_writer):
    """enter with portal not at position gives feedback."""
    from mudlib.commands.portals import cmd_enter

    await cmd_enter(player, "doorway")
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "don't see" in output.lower()


@pytest.mark.asyncio
async def test_enter_target_zone_not_found(player, test_zone, mock_writer):
    """enter with invalid target zone gives graceful error."""
    from mudlib.commands.portals import cmd_enter

    Portal(
        name="portal",
        location=test_zone,
        x=5,
        y=5,
        target_zone="nonexistent",
        target_x=3,
        target_y=7,
    )

    await cmd_enter(player, "portal")
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "doesn't lead anywhere" in output.lower() or "nowhere" in output.lower()
    # Player should still be in original zone
    assert player.location is test_zone


@pytest.mark.asyncio
async def test_enter_matches_aliases(player, test_zone, target_zone):
    """enter matches portal aliases."""
    from mudlib.commands.portals import cmd_enter

    register_zone("targetzone", target_zone)
    Portal(
        name="shimmering doorway",
        aliases=["door", "doorway"],
        location=test_zone,
        x=5,
        y=5,
        target_zone="targetzone",
        target_x=3,
        target_y=7,
    )

    await cmd_enter(player, "door")
    assert player.location is target_zone


@pytest.mark.asyncio
async def test_enter_portal_elsewhere_in_zone(player, test_zone, mock_writer):
    """enter doesn't find portals not at player position."""
    from mudlib.commands.portals import cmd_enter

    Portal(
        name="distant portal",
        location=test_zone,
        x=8,
        y=8,
        target_zone="targetzone",
        target_x=3,
        target_y=7,
    )

    await cmd_enter(player, "portal")
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "don't see" in output.lower()
    # Player should still be in original zone
    assert player.location is test_zone


# --- command registration ---


def test_enter_command_registered():
    """enter command is registered."""
    import mudlib.commands.portals  # noqa: F401

    assert "enter" in _registry
