"""Tests for verb dispatch fallback in command system."""

from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib.commands import dispatch
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,
    )


class Fountain(Thing):
    """Test object with drink verb."""

    @verb("drink")
    async def drink(self, player, args):
        await player.send("You drink from the fountain.\r\n")

    @verb("splash")
    async def splash(self, player, args):
        await player.send(f"You splash the fountain{' ' + args if args else ''}.\r\n")


@pytest.mark.asyncio
async def test_verb_dispatch_simple(player, test_zone):
    """Test 'drink fountain' dispatches to fountain's drink verb."""
    _fountain = Fountain(
        name="fountain",
        description="a stone fountain",
        portable=False,
        location=test_zone,
        x=5,
        y=5,
    )

    await dispatch(player, "drink fountain")

    player.writer.write.assert_called_once_with("You drink from the fountain.\r\n")
    player.writer.drain.assert_awaited_once()


@pytest.mark.asyncio
async def test_verb_dispatch_with_extra_args(player, test_zone):
    """Test 'splash fountain hard' passes 'hard' as extra args."""
    _fountain = Fountain(
        name="fountain",
        description="a stone fountain",
        portable=False,
        location=test_zone,
        x=5,
        y=5,
    )

    await dispatch(player, "splash fountain hard")

    player.writer.write.assert_called_once_with("You splash the fountain hard.\r\n")
    player.writer.drain.assert_awaited_once()


@pytest.mark.asyncio
async def test_verb_dispatch_object_lacks_verb(player, test_zone):
    """Test 'drink rock' fails if rock doesn't have drink verb."""
    _rock = Thing(
        name="rock",
        description="a plain rock",
        portable=True,
        location=test_zone,
        x=5,
        y=5,
    )

    await dispatch(player, "drink rock")

    player.writer.write.assert_called_once_with("Unknown command: drink\r\n")
    player.writer.drain.assert_awaited_once()


@pytest.mark.asyncio
async def test_verb_dispatch_object_not_found(player, test_zone):
    """Test 'drink flurb' fails if flurb doesn't exist."""
    await dispatch(player, "drink flurb")

    player.writer.write.assert_called_once_with("Unknown command: drink\r\n")
    player.writer.drain.assert_awaited_once()


@pytest.mark.asyncio
async def test_verb_dispatch_no_args(player, test_zone):
    """Test 'drink' with no args doesn't try verb dispatch."""
    await dispatch(player, "drink")

    player.writer.write.assert_called_once_with("Unknown command: drink\r\n")
    player.writer.drain.assert_awaited_once()


@pytest.mark.asyncio
async def test_verb_dispatch_doesnt_intercept_real_commands(player, test_zone):
    """Test that verb dispatch doesn't interfere with registered commands."""
    from mudlib.commands import CommandDefinition, register, unregister

    called = False

    async def test_command(player, args):
        nonlocal called
        called = True
        await player.send("Real command executed.\r\n")

    register(CommandDefinition("test", test_command))
    try:
        await dispatch(player, "test something")
        assert called
        player.writer.write.assert_called_once_with("Real command executed.\r\n")
    finally:
        unregister("test")


@pytest.mark.asyncio
async def test_verb_dispatch_finds_inventory_object(player, test_zone):
    """Test verb dispatch finds object in inventory."""

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

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

    await dispatch(player, "drink potion")

    player.writer.write.assert_called_once_with("You drink the potion.\r\n")
    player.writer.drain.assert_awaited_once()


@pytest.mark.asyncio
async def test_verb_dispatch_doesnt_fire_on_ambiguous_command(player, test_zone):
    """Test verb dispatch doesn't fire when command is ambiguous."""
    from mudlib.commands import CommandDefinition, register, unregister

    async def test_one(player, args):
        await player.send("test one\r\n")

    async def test_two(player, args):
        await player.send("test two\r\n")

    register(CommandDefinition("testone", test_one))
    register(CommandDefinition("testtwo", test_two))
    try:
        # "test" matches both testone and testtwo
        await dispatch(player, "test something")

        # Should get ambiguous command message, not verb dispatch
        written_text = player.writer.write.call_args[0][0].lower()
        # The ambiguous message is "testone or testtwo?" not "ambiguous"
        assert "or" in written_text and "?" in written_text
    finally:
        unregister("testone")
        unregister("testtwo")
