"""Tests for put and take-from commands."""

from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib.commands import _registry
from mudlib.container import Container
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


# --- cmd_put ---


@pytest.mark.asyncio
async def test_put_thing_in_container(player, test_zone, mock_writer):
    """put moves thing from inventory to container."""
    from mudlib.commands.containers import cmd_put

    chest = Container(
        name="chest", location=test_zone, x=5, y=5, closed=False, capacity=10
    )
    rock = Thing(name="rock", location=player)

    await cmd_put(player, "rock in chest")

    assert rock.location == chest
    assert rock in chest.contents
    assert rock not in player.contents
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "put" in output.lower() and "rock" in output.lower()
    assert "chest" in output.lower()


@pytest.mark.asyncio
async def test_put_no_args(player, mock_writer):
    """put with no args gives usage message."""
    from mudlib.commands.containers import cmd_put

    await cmd_put(player, "")
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "put" in output.lower() and "container" in output.lower()


@pytest.mark.asyncio
async def test_put_missing_container_arg(player, mock_writer):
    """put with missing container argument gives error."""
    from mudlib.commands.containers import cmd_put

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


@pytest.mark.asyncio
async def test_put_thing_not_in_inventory(player, test_zone, mock_writer):
    """put on thing not carried gives error."""
    from mudlib.commands.containers import cmd_put

    _chest = Container(
        name="chest", location=test_zone, x=5, y=5, closed=False, capacity=10
    )
    _rock = Thing(name="rock", location=test_zone, x=5, y=5)

    await cmd_put(player, "rock in chest")
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "not carrying" in output.lower() or "aren't carrying" in output.lower()


@pytest.mark.asyncio
async def test_put_container_not_found(player, mock_writer):
    """put into non-existent container gives error."""
    from mudlib.commands.containers import cmd_put

    _rock = Thing(name="rock", location=player)

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


@pytest.mark.asyncio
async def test_put_container_closed(player, test_zone, mock_writer):
    """put into closed container gives error."""
    from mudlib.commands.containers import cmd_put

    _chest = Container(
        name="chest", location=test_zone, x=5, y=5, closed=True, capacity=10
    )
    rock = Thing(name="rock", location=player)

    await cmd_put(player, "rock in chest")
    assert rock.location == player  # Should not have moved
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "closed" in output.lower()


@pytest.mark.asyncio
async def test_put_container_full(player, test_zone, mock_writer):
    """put into full container gives error."""
    from mudlib.commands.containers import cmd_put

    chest = Container(
        name="chest", location=test_zone, x=5, y=5, closed=False, capacity=1
    )
    # Fill the container
    _other = Thing(name="other", location=chest)
    rock = Thing(name="rock", location=player)

    await cmd_put(player, "rock in chest")
    assert rock.location == player  # Should not have moved
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "full" in output.lower()


@pytest.mark.asyncio
async def test_put_target_not_container(player, test_zone, mock_writer):
    """put into non-container thing gives error."""
    from mudlib.commands.containers import cmd_put

    _stick = Thing(name="stick", location=test_zone, x=5, y=5)
    rock = Thing(name="rock", location=player)

    await cmd_put(player, "rock in stick")
    assert rock.location == player  # Should not have moved
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "not a container" in output.lower() or "can't" in output.lower()


@pytest.mark.asyncio
async def test_put_matches_thing_alias(player, test_zone):
    """put matches thing by alias."""
    from mudlib.commands.containers import cmd_put

    chest = Container(
        name="chest", location=test_zone, x=5, y=5, closed=False, capacity=10
    )
    rock = Thing(name="small rock", aliases=["rock", "pebble"], location=player)

    await cmd_put(player, "pebble in chest")
    assert rock.location == chest


@pytest.mark.asyncio
async def test_put_matches_container_alias(player, test_zone):
    """put matches container by alias."""
    from mudlib.commands.containers import cmd_put

    chest = Container(
        name="wooden chest",
        aliases=["chest", "box"],
        location=test_zone,
        x=5,
        y=5,
        closed=False,
        capacity=10,
    )
    rock = Thing(name="rock", location=player)

    await cmd_put(player, "rock in box")
    assert rock.location == chest


@pytest.mark.asyncio
async def test_put_container_in_inventory(player, test_zone):
    """put works with container in player inventory."""
    from mudlib.commands.containers import cmd_put

    box = Container(name="box", location=player, closed=False, capacity=10)
    rock = Thing(name="rock", location=player)

    await cmd_put(player, "rock in box")
    assert rock.location == box


@pytest.mark.asyncio
async def test_put_container_in_itself(player, test_zone, mock_writer):
    """put container in itself gives error."""
    from mudlib.commands.containers import cmd_put

    box = Container(name="box", location=player, closed=False, capacity=10)

    await cmd_put(player, "box in box")
    assert box.location == player  # Should not have moved
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "can't put" in output.lower() and "itself" in output.lower()


# --- cmd_get with "from" (take-from) ---


@pytest.mark.asyncio
async def test_take_from_container(player, test_zone, mock_writer):
    """take from container moves thing to inventory."""
    from mudlib.commands.things import cmd_get

    chest = Container(
        name="chest", location=test_zone, x=5, y=5, closed=False, capacity=10
    )
    rock = Thing(name="rock", location=chest)

    await cmd_get(player, "rock from chest")

    assert rock.location == player
    assert rock in player.contents
    assert rock not in chest.contents
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "take" in output.lower() and "rock" in output.lower()
    assert "chest" in output.lower()


@pytest.mark.asyncio
async def test_take_from_no_args(player, mock_writer):
    """take from with missing args gives usage message."""
    from mudlib.commands.things import cmd_get

    await cmd_get(player, "from")
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "take" in output.lower() and "container" in output.lower()


@pytest.mark.asyncio
async def test_take_from_missing_container(player, test_zone, mock_writer):
    """take from with missing container gives error."""
    from mudlib.commands.things import cmd_get

    _chest = Container(
        name="chest", location=test_zone, x=5, y=5, closed=False, capacity=10
    )

    await cmd_get(player, "rock from")
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "take" in output.lower() and "container" in output.lower()


@pytest.mark.asyncio
async def test_take_from_container_not_found(player, test_zone, mock_writer):
    """take from non-existent container gives error."""
    from mudlib.commands.things import cmd_get

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


@pytest.mark.asyncio
async def test_take_from_closed_container(player, test_zone, mock_writer):
    """take from closed container gives error."""
    from mudlib.commands.things import cmd_get

    chest = Container(
        name="chest", location=test_zone, x=5, y=5, closed=True, capacity=10
    )
    _rock = Thing(name="rock", location=chest)

    await cmd_get(player, "rock from chest")
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "closed" in output.lower()


@pytest.mark.asyncio
async def test_take_from_thing_not_in_container(player, test_zone, mock_writer):
    """take from container when thing not inside gives error."""
    from mudlib.commands.things import cmd_get

    _chest = Container(
        name="chest", location=test_zone, x=5, y=5, closed=False, capacity=10
    )

    await cmd_get(player, "rock from chest")
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "doesn't contain" in output.lower() or "not in" in output.lower()


@pytest.mark.asyncio
async def test_take_from_non_container(player, test_zone, mock_writer):
    """take from non-container thing gives error."""
    from mudlib.commands.things import cmd_get

    _stick = Thing(name="stick", location=test_zone, x=5, y=5)

    await cmd_get(player, "rock from stick")
    output = mock_writer.write.call_args_list[-1][0][0]
    assert "not a container" in output.lower() or "can't" in output.lower()


@pytest.mark.asyncio
async def test_take_from_matches_thing_alias(player, test_zone):
    """take from matches thing by alias."""
    from mudlib.commands.things import cmd_get

    chest = Container(
        name="chest", location=test_zone, x=5, y=5, closed=False, capacity=10
    )
    rock = Thing(name="small rock", aliases=["rock", "pebble"], location=chest)

    await cmd_get(player, "pebble from chest")
    assert rock.location == player


@pytest.mark.asyncio
async def test_take_from_matches_container_alias(player, test_zone):
    """take from matches container by alias."""
    from mudlib.commands.things import cmd_get

    chest = Container(
        name="wooden chest",
        aliases=["chest", "box"],
        location=test_zone,
        x=5,
        y=5,
        closed=False,
        capacity=10,
    )
    rock = Thing(name="rock", location=chest)

    await cmd_get(player, "rock from box")
    assert rock.location == player


@pytest.mark.asyncio
async def test_take_from_container_in_inventory(player, test_zone):
    """take from works with container in player inventory."""
    from mudlib.commands.things import cmd_get

    box = Container(name="box", location=player, closed=False, capacity=10)
    rock = Thing(name="rock", location=box)

    await cmd_get(player, "rock from box")
    assert rock.location == player


# --- command registration ---


def test_put_command_registered():
    """put command is registered."""
    import mudlib.commands.containers  # noqa: F401

    assert "put" in _registry
