from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib.commands.examine import cmd_examine
from mudlib.container import Container
from mudlib.entity import Mob
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, 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,
    )


@pytest.mark.asyncio
async def test_examine_no_args(player, mock_writer):
    """examine with no args prompts what to examine"""
    await cmd_examine(player, "")

    output = "".join(call[0][0] for call in mock_writer.write.call_args_list)
    assert "Examine what?" in output


@pytest.mark.asyncio
async def test_examine_thing_on_ground_by_name(player, test_zone, mock_writer):
    """examine finds Thing on ground by name and shows description"""
    _rock = Thing(
        name="rock",
        x=5,
        y=5,
        location=test_zone,
        description="A smooth gray rock with moss on one side.",
    )

    await cmd_examine(player, "rock")

    output = "".join(call[0][0] for call in mock_writer.write.call_args_list)
    assert "A smooth gray rock with moss on one side." in output


@pytest.mark.asyncio
async def test_examine_thing_on_ground_by_alias(player, test_zone, mock_writer):
    """examine finds Thing on ground by alias"""
    _rock = Thing(
        name="rock",
        x=5,
        y=5,
        location=test_zone,
        aliases=["stone"],
        description="A smooth gray rock.",
    )

    await cmd_examine(player, "stone")

    output = "".join(call[0][0] for call in mock_writer.write.call_args_list)
    assert "A smooth gray rock." in output


@pytest.mark.asyncio
async def test_examine_thing_in_inventory_by_name(player, test_zone, mock_writer):
    """examine finds Thing in inventory by name"""
    _key = Thing(
        name="key",
        x=5,
        y=5,
        location=player,
        description="A rusty iron key.",
    )

    await cmd_examine(player, "key")

    output = "".join(call[0][0] for call in mock_writer.write.call_args_list)
    assert "A rusty iron key." in output


@pytest.mark.asyncio
async def test_examine_inventory_before_ground(player, test_zone, mock_writer):
    """examine finds in inventory before ground"""
    _ground_rock = Thing(
        name="rock",
        x=5,
        y=5,
        location=test_zone,
        description="A rock on the ground.",
    )
    _inventory_rock = Thing(
        name="rock",
        x=5,
        y=5,
        location=player,
        description="A rock in your pocket.",
    )

    await cmd_examine(player, "rock")

    output = "".join(call[0][0] for call in mock_writer.write.call_args_list)
    assert "A rock in your pocket." in output
    assert "A rock on the ground." not in output


@pytest.mark.asyncio
async def test_examine_mob_at_position(player, test_zone, mock_writer):
    """examine finds Mob at same position and shows description"""
    _goblin = Mob(
        name="goblin",
        x=5,
        y=5,
        location=test_zone,
        description="A small, greenish creature with beady eyes.",
    )

    await cmd_examine(player, "goblin")

    output = "".join(call[0][0] for call in mock_writer.write.call_args_list)
    assert "A small, greenish creature with beady eyes." in output


@pytest.mark.asyncio
async def test_examine_not_found(player, mock_writer):
    """examine nonexistent object shows not found message"""
    await cmd_examine(player, "flurb")

    output = "".join(call[0][0] for call in mock_writer.write.call_args_list)
    assert "You don't see that here." in output


@pytest.mark.asyncio
async def test_examine_thing_with_empty_description(player, test_zone, mock_writer):
    """examine thing with empty description shows default message"""
    _rock = Thing(
        name="rock",
        x=5,
        y=5,
        location=test_zone,
        description="",
    )

    await cmd_examine(player, "rock")

    output = "".join(call[0][0] for call in mock_writer.write.call_args_list)
    assert "You see nothing special." in output


@pytest.mark.asyncio
async def test_examine_container_shows_description(player, test_zone, mock_writer):
    """examine Container shows description"""
    _chest = Container(
        name="chest",
        x=5,
        y=5,
        location=test_zone,
        description="A sturdy wooden chest with brass hinges.",
        closed=False,
    )

    await cmd_examine(player, "chest")

    output = "".join(call[0][0] for call in mock_writer.write.call_args_list)
    assert "A sturdy wooden chest with brass hinges." in output


@pytest.mark.asyncio
async def test_examine_alias_ex(player, test_zone, mock_writer):
    """ex alias works"""
    _rock = Thing(
        name="rock",
        x=5,
        y=5,
        location=test_zone,
        description="A smooth rock.",
    )

    # The command registration will handle the alias, but test function
    await cmd_examine(player, "rock")

    output = "".join(call[0][0] for call in mock_writer.write.call_args_list)
    assert "A smooth rock." in output
