"""Tests for terrain editing command."""

from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib.commands.terrain import cmd_terrain
from mudlib.housing import init_housing
from mudlib.player import Player
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_home_zone(player_name="alice"):
    """Create a home zone matching housing.py format."""
    name = f"home:{player_name}"
    terrain = []
    for y in range(9):
        row = []
        for x in range(9):
            if x == 0 or x == 8 or y == 0 or y == 8:
                row.append("#")
            else:
                row.append(".")
        terrain.append(row)
    zone = Zone(
        name=name,
        description=f"{player_name}'s home",
        width=9,
        height=9,
        terrain=terrain,
        toroidal=False,
        impassable={"#", "^", "~"},
        spawn_x=4,
        spawn_y=4,
        safe=True,
    )
    register_zone(name, zone)
    return zone


def _make_player(name="alice", zone=None, x=4, y=4):
    """Create a test player with mock writer."""
    mock_writer = MagicMock()
    mock_writer.write = MagicMock()
    mock_writer.drain = AsyncMock()
    p = Player(name=name, location=zone, x=x, y=y, writer=mock_writer)
    p.home_zone = f"home:{name}"
    return p


@pytest.mark.asyncio
async def test_terrain_paint_tile(tmp_path):
    """terrain <tile> changes terrain at current position."""
    init_housing(tmp_path)
    zone = _make_home_zone("alice")
    player = _make_player("alice", zone=zone, x=4, y=4)

    # Verify starting terrain
    assert zone.terrain[4][4] == "."

    # Paint a new tile
    await cmd_terrain(player, "~")

    # Verify terrain changed
    assert zone.terrain[4][4] == "~"

    # Verify success message
    player.writer.write.assert_called()
    messages = "".join(call[0][0] for call in player.writer.write.call_args_list)
    assert "paint" in messages.lower()


@pytest.mark.asyncio
async def test_terrain_not_in_home_zone():
    """terrain command fails when not in home zone."""
    # Create home zone but place player in different zone
    _make_home_zone("alice")
    overworld = Zone(
        name="overworld",
        description="The overworld",
        width=50,
        height=50,
        terrain=[["."] * 50 for _ in range(50)],
        toroidal=True,
        impassable=set(),
        spawn_x=25,
        spawn_y=25,
        safe=False,
    )
    register_zone("overworld", overworld)
    player = _make_player("alice", zone=overworld, x=25, y=25)

    await cmd_terrain(player, "~")

    # Verify error message
    player.writer.write.assert_called()
    messages = "".join(call[0][0] for call in player.writer.write.call_args_list)
    assert "home" in messages.lower() or "can't" in messages.lower()


@pytest.mark.asyncio
async def test_terrain_cannot_edit_border():
    """terrain command prevents editing border tiles."""
    zone = _make_home_zone("alice")

    # Test all border positions
    border_positions = [
        (0, 0),  # top-left corner
        (4, 0),  # top edge
        (8, 0),  # top-right corner
        (0, 4),  # left edge
        (8, 4),  # right edge
        (0, 8),  # bottom-left corner
        (4, 8),  # bottom edge
        (8, 8),  # bottom-right corner
    ]

    for x, y in border_positions:
        player = _make_player("alice", zone=zone, x=x, y=y)

        await cmd_terrain(player, ".")

        # Verify error message
        player.writer.write.assert_called()
        messages = "".join(call[0][0] for call in player.writer.write.call_args_list)
        assert "border" in messages.lower() or "wall" in messages.lower()

        # Verify terrain unchanged
        assert zone.terrain[y][x] == "#"


@pytest.mark.asyncio
async def test_terrain_no_args():
    """terrain with no args shows usage."""
    zone = _make_home_zone("alice")
    player = _make_player("alice", zone=zone, x=4, y=4)

    await cmd_terrain(player, "")

    # Verify usage message
    player.writer.write.assert_called()
    messages = "".join(call[0][0] for call in player.writer.write.call_args_list)
    assert "usage" in messages.lower() or "terrain <" in messages.lower()


@pytest.mark.asyncio
async def test_terrain_only_single_char():
    """terrain rejects multi-character arguments."""
    zone = _make_home_zone("alice")
    player = _make_player("alice", zone=zone, x=4, y=4)

    await cmd_terrain(player, "~~")

    # Verify error message
    player.writer.write.assert_called()
    messages = "".join(call[0][0] for call in player.writer.write.call_args_list)
    assert "single" in messages.lower() or "one character" in messages.lower()

    # Verify terrain unchanged
    assert zone.terrain[4][4] == "."


@pytest.mark.asyncio
async def test_terrain_saves_zone(tmp_path):
    """terrain command saves zone to TOML after edit."""
    import tomllib

    init_housing(tmp_path)
    zone = _make_home_zone("alice")
    player = _make_player("alice", zone=zone, x=4, y=4)

    # Paint a tile
    await cmd_terrain(player, "~")

    # Verify TOML file was updated
    zone_file = tmp_path / "alice.toml"
    assert zone_file.exists()

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

    # Check that row 4 contains the water tile at position 4
    rows = data["terrain"]["rows"]
    assert rows[4][4] == "~"


@pytest.mark.asyncio
async def test_terrain_various_tiles(tmp_path):
    """terrain accepts various tile characters."""
    init_housing(tmp_path)
    zone = _make_home_zone("alice")

    test_tiles = [".", "~", "^", "T", ",", '"', "*", "+", "="]

    for tile in test_tiles:
        player = _make_player("alice", zone=zone, x=4, y=4)
        await cmd_terrain(player, tile)
        assert zone.terrain[4][4] == tile
