"""Tests for furnish and unfurnish commands."""

from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib.housing import init_housing
from mudlib.player import Player
from mudlib.thing import Thing
from mudlib.zone import Zone
from mudlib.zones import register_zone, zone_registry


@pytest.fixture(autouse=True)
def _clean_registries():
    """Clear zone registry between tests."""
    saved = dict(zone_registry)
    zone_registry.clear()
    yield
    zone_registry.clear()
    zone_registry.update(saved)


def _make_zone(name="overworld", width=20, height=20):
    """Create a zone for testing."""
    terrain = [["." for _ in range(width)] for _ in range(height)]
    zone = Zone(
        name=name,
        description=name,
        width=width,
        height=height,
        terrain=terrain,
        toroidal=True,
    )
    register_zone(name, zone)
    return zone


def _make_player(name="tester", zone=None, x=5, y=5):
    """Create a player with mock writer."""
    mock_writer = MagicMock()
    mock_writer.write = MagicMock()
    mock_writer.drain = AsyncMock()
    return Player(name=name, location=zone, x=x, y=y, writer=mock_writer)


@pytest.mark.asyncio
async def test_furnish_places_item(tmp_path):
    """furnish moves an item from inventory to the zone at player position."""
    from mudlib.commands.furnish import cmd_furnish

    init_housing(tmp_path)

    # Create home zone and player
    home_zone = _make_zone("home:alice", width=9, height=9)
    player = _make_player("alice", zone=home_zone, x=4, y=4)
    player.home_zone = "home:alice"

    # Give player a chair
    chair = Thing(name="chair", description="A wooden chair")
    chair.move_to(player)

    # Furnish the chair
    await cmd_furnish(player, "chair")

    # Chair should be in the zone at player position
    assert chair.location is home_zone
    assert chair.x == 4
    assert chair.y == 4
    assert chair not in player.contents

    # Player should get feedback
    player.writer.write.assert_called()
    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "chair" in output.lower()


@pytest.mark.asyncio
async def test_furnish_not_in_home_zone():
    """furnish fails if player is not in their home zone."""
    from mudlib.commands.furnish import cmd_furnish

    overworld = _make_zone("overworld")
    player = _make_player("alice", zone=overworld)
    player.home_zone = "home:alice"

    chair = Thing(name="chair")
    chair.move_to(player)

    await cmd_furnish(player, "chair")

    # Chair should still be in inventory
    assert chair.location is player
    assert chair in player.contents

    # Player should get error message
    player.writer.write.assert_called()
    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "home zone" in output.lower()


@pytest.mark.asyncio
async def test_furnish_item_not_in_inventory():
    """furnish fails if item is not in player inventory."""
    from mudlib.commands.furnish import cmd_furnish

    home_zone = _make_zone("home:alice", width=9, height=9)
    player = _make_player("alice", zone=home_zone)
    player.home_zone = "home:alice"

    await cmd_furnish(player, "chair")

    # Player should get error message
    player.writer.write.assert_called()
    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "don't have" in output.lower() or "not carrying" in output.lower()


@pytest.mark.asyncio
async def test_furnish_no_args():
    """furnish fails with usage message if no args provided."""
    from mudlib.commands.furnish import cmd_furnish

    home_zone = _make_zone("home:alice", width=9, height=9)
    player = _make_player("alice", zone=home_zone)
    player.home_zone = "home:alice"

    await cmd_furnish(player, "")

    # Player should get usage message
    player.writer.write.assert_called()
    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "usage" in output.lower() or "furnish" in output.lower()


@pytest.mark.asyncio
async def test_unfurnish_picks_up_item(tmp_path):
    """unfurnish moves furniture from zone to player inventory."""
    from mudlib.commands.furnish import cmd_unfurnish

    init_housing(tmp_path)

    # Create home zone with furniture
    home_zone = _make_zone("home:bob", width=9, height=9)
    player = _make_player("bob", zone=home_zone, x=4, y=4)
    player.home_zone = "home:bob"

    # Place a table at player position
    table = Thing(name="table", description="A wooden table")
    table.move_to(home_zone, x=4, y=4)

    # Unfurnish the table
    await cmd_unfurnish(player, "table")

    # Table should be in inventory
    assert table.location is player
    assert table in player.contents
    assert table not in home_zone._contents

    # Player should get feedback
    player.writer.write.assert_called()
    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "table" in output.lower()


@pytest.mark.asyncio
async def test_unfurnish_not_in_home_zone():
    """unfurnish fails if player is not in their home zone."""
    from mudlib.commands.furnish import cmd_unfurnish

    overworld = _make_zone("overworld")
    player = _make_player("bob", zone=overworld)
    player.home_zone = "home:bob"

    # Place a table
    table = Thing(name="table")
    table.move_to(overworld, x=5, y=5)

    await cmd_unfurnish(player, "table")

    # Table should still be on ground
    assert table.location is overworld
    assert table not in player.contents

    # Player should get error message
    player.writer.write.assert_called()
    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "home zone" in output.lower()


@pytest.mark.asyncio
async def test_unfurnish_nothing_at_position():
    """unfurnish fails if no matching furniture at player position."""
    from mudlib.commands.furnish import cmd_unfurnish

    home_zone = _make_zone("home:bob", width=9, height=9)
    player = _make_player("bob", zone=home_zone, x=4, y=4)
    player.home_zone = "home:bob"

    # No furniture at position
    await cmd_unfurnish(player, "table")

    # Player should get error message
    player.writer.write.assert_called()
    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert (
        "no" in output.lower()
        or "don't see" in output.lower()
        or "can't find" in output.lower()
    )


@pytest.mark.asyncio
async def test_unfurnish_no_args():
    """unfurnish fails with usage message if no args provided."""
    from mudlib.commands.furnish import cmd_unfurnish

    home_zone = _make_zone("home:bob", width=9, height=9)
    player = _make_player("bob", zone=home_zone)
    player.home_zone = "home:bob"

    await cmd_unfurnish(player, "")

    # Player should get usage message
    player.writer.write.assert_called()
    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "usage" in output.lower() or "unfurnish" in output.lower()


@pytest.mark.asyncio
async def test_furnish_saves_zone(tmp_path):
    """furnish persists furniture to the zone TOML file."""
    import tomllib

    from mudlib.commands.furnish import cmd_furnish

    init_housing(tmp_path)

    # Create home zone and player
    home_zone = _make_zone("home:charlie", width=9, height=9)
    player = _make_player("charlie", zone=home_zone, x=4, y=4)
    player.home_zone = "home:charlie"

    # Give player a lamp
    lamp = Thing(name="lamp", description="A brass lamp")
    lamp.move_to(player)

    # Furnish it
    await cmd_furnish(player, "lamp")

    # Check TOML file
    zone_file = tmp_path / "charlie.toml"
    assert zone_file.exists()

    with open(zone_file, "rb") as f:
        data = tomllib.load(f)

    # Should have furniture entry
    furniture = data.get("furniture", [])
    assert len(furniture) == 1
    assert furniture[0]["template"] == "lamp"
    assert furniture[0]["x"] == 4
    assert furniture[0]["y"] == 4
