"""Tests for get and drop commands."""

from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib.commands import _registry
from mudlib.entity import Entity
from mudlib.player import Player
from mudlib.thing import Thing
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
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 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


# --- Object.move_to ---


def test_move_to_updates_location():
    """move_to changes the object's location pointer."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    zone = Zone(name="test", width=10, height=10, terrain=terrain)
    entity = Entity(name="player", location=zone, x=5, y=5)
    rock = Thing(name="rock", location=zone, x=5, y=5)

    rock.move_to(entity)
    assert rock.location is entity


def test_move_to_removes_from_old_contents():
    """move_to removes object from old location's contents."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    zone = Zone(name="test", width=10, height=10, terrain=terrain)
    entity = Entity(name="player", location=zone, x=5, y=5)
    rock = Thing(name="rock", location=zone, x=5, y=5)

    rock.move_to(entity)
    assert rock not in zone.contents


def test_move_to_adds_to_new_contents():
    """move_to adds object to new location's contents."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    zone = Zone(name="test", width=10, height=10, terrain=terrain)
    entity = Entity(name="player", location=zone, x=5, y=5)
    rock = Thing(name="rock", location=zone, x=5, y=5)

    rock.move_to(entity)
    assert rock in entity.contents


def test_move_to_from_none():
    """move_to works from location=None (template to world)."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    zone = Zone(name="test", width=10, height=10, terrain=terrain)
    rock = Thing(name="rock")

    rock.move_to(zone, x=3, y=7)
    assert rock.location is zone
    assert rock.x == 3
    assert rock.y == 7
    assert rock in zone.contents


def test_move_to_sets_coordinates():
    """move_to can set x/y coordinates."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    zone = Zone(name="test", width=10, height=10, terrain=terrain)
    rock = Thing(name="rock", location=zone, x=1, y=1)

    entity = Entity(name="player", location=zone, x=5, y=5)
    rock.move_to(entity)
    # When moving to inventory, coordinates should be cleared
    assert rock.x is None
    assert rock.y is None


def test_move_to_zone_sets_coordinates():
    """move_to a zone sets x/y from args."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    zone = Zone(name="test", width=10, height=10, terrain=terrain)
    entity = Entity(name="player", location=zone, x=5, y=5)
    rock = Thing(name="rock", location=entity)

    rock.move_to(zone, x=3, y=7)
    assert rock.x == 3
    assert rock.y == 7


# --- cmd_get ---


@pytest.mark.asyncio
async def test_get_picks_up_thing(player, test_zone):
    """get moves a thing from zone to player inventory."""
    from mudlib.commands.things import cmd_get

    rock = Thing(name="rock", location=test_zone, x=5, y=5)
    await cmd_get(player, "rock")
    assert rock.location is player
    assert rock in player.contents
    assert rock not in test_zone.contents


@pytest.mark.asyncio
async def test_get_sends_confirmation(player, test_zone, mock_writer):
    """get sends feedback to the player."""
    from mudlib.commands.things import cmd_get

    Thing(name="rock", location=test_zone, x=5, y=5)
    await cmd_get(player, "rock")
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "rock" in output.lower()


@pytest.mark.asyncio
async def test_get_nothing_there(player, test_zone, mock_writer):
    """get with no matching item gives feedback."""
    from mudlib.commands.things import cmd_get

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


@pytest.mark.asyncio
async def test_get_non_portable(player, test_zone, mock_writer):
    """get rejects non-portable things."""
    from mudlib.commands.things import cmd_get

    fountain = Thing(
        name="fountain",
        location=test_zone,
        x=5,
        y=5,
        portable=False,
    )
    await cmd_get(player, "fountain")
    # Fountain should still be in zone, not in player
    assert fountain.location is test_zone
    assert fountain not in player.contents
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "can't" in output.lower()


@pytest.mark.asyncio
async def test_get_no_args(player, mock_writer):
    """get with no arguments gives usage hint."""
    from mudlib.commands.things import cmd_get

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


@pytest.mark.asyncio
async def test_get_matches_aliases(player, test_zone):
    """get matches thing aliases."""
    from mudlib.commands.things import cmd_get

    can = Thing(
        name="pepsi can",
        aliases=["can", "pepsi"],
        location=test_zone,
        x=5,
        y=5,
    )
    await cmd_get(player, "pepsi")
    assert can.location is player


# --- cmd_drop ---


@pytest.mark.asyncio
async def test_drop_puts_thing_on_ground(player, test_zone):
    """drop moves a thing from inventory to zone at player's position."""
    from mudlib.commands.things import cmd_drop

    rock = Thing(name="rock", location=player)
    await cmd_drop(player, "rock")
    assert rock.location is test_zone
    assert rock.x == player.x
    assert rock.y == player.y
    assert rock in test_zone.contents
    assert rock not in player.contents


@pytest.mark.asyncio
async def test_drop_sends_confirmation(player, test_zone, mock_writer):
    """drop sends feedback to the player."""
    from mudlib.commands.things import cmd_drop

    Thing(name="rock", location=player)
    await cmd_drop(player, "rock")
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "rock" in output.lower()


@pytest.mark.asyncio
async def test_drop_not_carrying(player, mock_writer):
    """drop with item not in inventory gives feedback."""
    from mudlib.commands.things import cmd_drop

    await cmd_drop(player, "sword")
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "not carrying" in output.lower() or "don't have" in output.lower()


@pytest.mark.asyncio
async def test_drop_no_args(player, mock_writer):
    """drop with no arguments gives usage hint."""
    from mudlib.commands.things import cmd_drop

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


@pytest.mark.asyncio
async def test_drop_requires_zone(player, mock_writer):
    """drop without a zone location gives error."""
    from mudlib.commands.things import cmd_drop

    player.location = None
    Thing(name="rock", location=player)
    await cmd_drop(player, "rock")
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "nowhere" in output.lower() or "can't" in output.lower()


@pytest.mark.asyncio
async def test_drop_matches_aliases(player, test_zone):
    """drop matches thing aliases."""
    from mudlib.commands.things import cmd_drop

    can = Thing(
        name="pepsi can",
        aliases=["can", "pepsi"],
        location=player,
    )
    await cmd_drop(player, "can")
    assert can.location is test_zone


# --- command registration ---


def test_get_command_registered():
    """get command is registered."""
    import mudlib.commands.things  # noqa: F401

    assert "get" in _registry


def test_drop_command_registered():
    """drop command is registered."""
    import mudlib.commands.things  # noqa: F401

    assert "drop" in _registry
