from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib.commands.use import cmd_use
from mudlib.player import Player
from mudlib.thing import Thing
from mudlib.verbs import verb
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, terrain=terrain)


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


def get_output(mock_writer):
    """Extract all text written to the mock writer."""
    return "".join(call[0][0] for call in mock_writer.write.call_args_list)


@pytest.mark.asyncio
async def test_use_no_args(player, mock_writer):
    """use with no args should prompt for what to use"""
    await cmd_use(player, "")
    output = get_output(mock_writer)
    assert "Use what?" in output


@pytest.mark.asyncio
async def test_use_object_with_verb(player, mock_writer, test_zone):
    """use rock should find rock with use verb and call it"""

    class Rock(Thing):
        @verb("use")
        async def use_verb(self, player, args):
            await player.send("You use the rock.\r\n")

    _rock = Rock(
        name="rock", description="a smooth rock", portable=True, location=test_zone
    )
    _rock.x = 5
    _rock.y = 5

    await cmd_use(player, "rock")
    output = get_output(mock_writer)
    assert "You use the rock." in output


@pytest.mark.asyncio
async def test_use_object_without_verb(player, mock_writer, test_zone):
    """use rock when rock has no use verb should give error"""
    _rock = Thing(
        name="rock", description="a smooth rock", portable=True, location=test_zone
    )
    _rock.x = 5
    _rock.y = 5

    await cmd_use(player, "rock")
    output = get_output(mock_writer)
    assert "You can't use that." in output


@pytest.mark.asyncio
async def test_use_object_not_found(player, mock_writer):
    """use flurb when nothing matches should give error"""
    await cmd_use(player, "flurb")
    output = get_output(mock_writer)
    assert "You don't see that here." in output


@pytest.mark.asyncio
async def test_use_object_on_target(player, mock_writer, test_zone):
    """use key on chest should pass chest as args to verb"""

    class Key(Thing):
        @verb("use")
        async def use_verb(self, player, args):
            await player.send(f"You use the key on {args}.\r\n")

    _key = Key(name="key", description="a brass key", portable=True, location=test_zone)
    _key.x = 5
    _key.y = 5

    await cmd_use(player, "key on chest")
    output = get_output(mock_writer)
    assert "You use the key on chest." in output


@pytest.mark.asyncio
async def test_use_object_on_nonexistent_target(player, mock_writer, test_zone):
    """use key on flurb should still work, passing flurb to verb handler"""

    class Key(Thing):
        @verb("use")
        async def use_verb(self, player, args):
            await player.send(f"You use the key on {args}.\r\n")

    _key = Key(name="key", description="a brass key", portable=True, location=test_zone)
    _key.x = 5
    _key.y = 5

    await cmd_use(player, "key on flurb")
    output = get_output(mock_writer)
    assert "You use the key on flurb." in output


@pytest.mark.asyncio
async def test_use_object_in_inventory(player, mock_writer):
    """use should find objects in inventory"""

    class Potion(Thing):
        @verb("use")
        async def use_verb(self, player, args):
            await player.send("You drink the potion.\r\n")

    _potion = Potion(
        name="potion", description="a healing potion", portable=True, location=player
    )

    await cmd_use(player, "potion")
    output = get_output(mock_writer)
    assert "You drink the potion." in output


@pytest.mark.asyncio
async def test_use_passes_correct_args(player, mock_writer, test_zone):
    """verify verb handler receives correct args string"""
    received_args = None

    class Tool(Thing):
        @verb("use")
        async def use_verb(self, player, args):
            nonlocal received_args
            received_args = args
            await player.send(f"Used with args: '{args}'\r\n")

    _tool = Tool(
        name="tool", description="a multi-tool", portable=True, location=test_zone
    )
    _tool.x = 5
    _tool.y = 5

    # Test with no target
    await cmd_use(player, "tool")
    assert received_args == ""

    # Reset writer
    mock_writer.write.reset_mock()
    received_args = None

    # Test with target
    await cmd_use(player, "tool on something")
    assert received_args == "something"
