"""Tests for zone boundary regions."""

from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib.export import export_zone
from mudlib.player import Player
from mudlib.thing import Thing
from mudlib.zone import BoundaryRegion, Zone
from mudlib.zones import load_zone


def create_mock_writer():
    """Create a mock writer for testing."""
    writer = MagicMock()
    writer.write = MagicMock()
    writer.drain = AsyncMock()
    return writer


def test_boundary_contains():
    """Test BoundaryRegion.contains() method."""
    boundary = BoundaryRegion(
        name="test_area",
        x_min=10,
        x_max=15,
        y_min=8,
        y_max=12,
    )

    # Inside the boundary
    assert boundary.contains(10, 8)
    assert boundary.contains(15, 12)
    assert boundary.contains(12, 10)

    # Outside the boundary
    assert not boundary.contains(9, 10)
    assert not boundary.contains(16, 10)
    assert not boundary.contains(12, 7)
    assert not boundary.contains(12, 13)


def test_boundary_parsing_from_toml(tmp_path):
    """Test loading boundaries from TOML."""
    toml_content = """
name = "test_zone"
width = 20
height = 20

[terrain]
rows = [
    ".....................",
    ".....................",
    ".....................",
]

[terrain.impassable]
tiles = ["^"]

[[boundaries]]
name = "vault"
x = [10, 15]
y = [8, 12]
on_enter_message = "You enter the vault."
on_exit_message = "You leave the vault."
on_exit_check = "carrying:treasure"
on_exit_fail = "Guards block your path!"

[[boundaries]]
name = "simple"
x = [0, 5]
y = [0, 5]
"""

    zone_file = tmp_path / "test.toml"
    zone_file.write_text(toml_content)

    zone = load_zone(zone_file)

    assert len(zone.boundaries) == 2

    vault = zone.boundaries[0]
    assert vault.name == "vault"
    assert vault.x_min == 10
    assert vault.x_max == 15
    assert vault.y_min == 8
    assert vault.y_max == 12
    assert vault.on_enter_message == "You enter the vault."
    assert vault.on_exit_message == "You leave the vault."
    assert vault.on_exit_check == "carrying:treasure"
    assert vault.on_exit_fail == "Guards block your path!"

    simple = zone.boundaries[1]
    assert simple.name == "simple"
    assert simple.x_min == 0
    assert simple.x_max == 5
    assert simple.y_min == 0
    assert simple.y_max == 5
    assert simple.on_enter_message is None
    assert simple.on_exit_message is None
    assert simple.on_exit_check is None
    assert simple.on_exit_fail is None


def test_boundary_export_to_toml():
    """Test exporting boundaries to TOML."""
    zone = Zone(
        name="test_zone",
        width=20,
        height=20,
        terrain=[["." for _ in range(20)] for _ in range(20)],
        boundaries=[
            BoundaryRegion(
                name="vault",
                x_min=10,
                x_max=15,
                y_min=8,
                y_max=12,
                on_enter_message="You enter the vault.",
                on_exit_message="You leave the vault.",
                on_exit_check="carrying:treasure",
                on_exit_fail="Guards block your path!",
            ),
        ],
    )

    toml_str = export_zone(zone)

    assert "[[boundaries]]" in toml_str
    assert 'name = "vault"' in toml_str
    assert "x = [10, 15]" in toml_str
    assert "y = [8, 12]" in toml_str
    assert 'on_enter_message = "You enter the vault."' in toml_str
    assert 'on_exit_message = "You leave the vault."' in toml_str
    assert 'on_exit_check = "carrying:treasure"' in toml_str
    assert 'on_exit_fail = "Guards block your path!"' in toml_str


def test_boundary_roundtrip(tmp_path):
    """Test TOML -> load -> export -> load round-trip."""
    original = Zone(
        name="test_zone",
        width=20,
        height=20,
        terrain=[["." for _ in range(20)] for _ in range(20)],
        boundaries=[
            BoundaryRegion(
                name="area1",
                x_min=5,
                x_max=10,
                y_min=5,
                y_max=10,
                on_enter_message="Enter area1",
            ),
        ],
    )

    # Export to file
    zone_file = tmp_path / "test.toml"
    toml_str = export_zone(original)
    zone_file.write_text(toml_str)

    # Load it back
    loaded = load_zone(zone_file)

    # Verify boundaries survived the round-trip
    assert len(loaded.boundaries) == 1
    assert loaded.boundaries[0].name == "area1"
    assert loaded.boundaries[0].x_min == 5
    assert loaded.boundaries[0].x_max == 10
    assert loaded.boundaries[0].on_enter_message == "Enter area1"


@pytest.mark.asyncio
async def test_boundary_enter_message():
    """Test on_enter_message sent when entering boundary."""
    from mudlib.commands.movement import move_player

    zone = Zone(
        name="test",
        width=20,
        height=20,
        terrain=[["." for _ in range(20)] for _ in range(20)],
        boundaries=[
            BoundaryRegion(
                name="vault",
                x_min=10,
                x_max=15,
                y_min=10,
                y_max=15,
                on_enter_message="You step into the vault.",
            ),
        ],
    )

    writer = create_mock_writer()
    player = Player(name="TestPlayer", location=zone, x=9, y=10, writer=writer)

    # Move east into the boundary
    await move_player(player, 1, 0, "east")

    # Check that the message was written
    written_text = "".join(call[0][0] for call in writer.write.call_args_list)
    assert "You step into the vault." in written_text


@pytest.mark.asyncio
async def test_boundary_exit_message():
    """Test on_exit_message sent when leaving boundary."""
    from mudlib.commands.movement import move_player

    zone = Zone(
        name="test",
        width=20,
        height=20,
        terrain=[["." for _ in range(20)] for _ in range(20)],
        boundaries=[
            BoundaryRegion(
                name="vault",
                x_min=10,
                x_max=15,
                y_min=10,
                y_max=15,
                on_exit_message="You leave the vault.",
            ),
        ],
    )

    writer = create_mock_writer()
    player = Player(name="TestPlayer", location=zone, x=15, y=10, writer=writer)

    # Move east out of the boundary
    await move_player(player, 1, 0, "east")

    written_text = "".join(call[0][0] for call in writer.write.call_args_list)
    assert "You leave the vault." in written_text


@pytest.mark.asyncio
async def test_carrying_check_blocks_exit():
    """Test carrying check blocks movement when holding matching item."""
    from mudlib.commands.movement import move_player

    zone = Zone(
        name="test",
        width=20,
        height=20,
        terrain=[["." for _ in range(20)] for _ in range(20)],
        boundaries=[
            BoundaryRegion(
                name="vault",
                x_min=10,
                x_max=15,
                y_min=10,
                y_max=15,
                on_exit_check="carrying:treasure",
                on_exit_fail="Guards block your path!",
            ),
        ],
    )

    writer = create_mock_writer()
    player = Player(name="TestPlayer", location=zone, x=15, y=10, writer=writer)

    # Give player an item named "treasure"
    Thing(name="treasure", location=player)

    # Try to move east out of boundary
    await move_player(player, 1, 0, "east")

    written_text = "".join(call[0][0] for call in writer.write.call_args_list)
    assert "Guards block your path!" in written_text
    # Player should still be at original position
    assert player.x == 15
    assert player.y == 10


@pytest.mark.asyncio
async def test_carrying_check_allows_exit_without_item():
    """Test carrying check allows movement without matching item."""
    from mudlib.commands.movement import move_player

    zone = Zone(
        name="test",
        width=20,
        height=20,
        terrain=[["." for _ in range(20)] for _ in range(20)],
        boundaries=[
            BoundaryRegion(
                name="vault",
                x_min=10,
                x_max=15,
                y_min=10,
                y_max=15,
                on_exit_check="carrying:treasure",
                on_exit_fail="Guards block your path!",
            ),
        ],
    )

    writer = create_mock_writer()
    player = Player(name="TestPlayer", location=zone, x=15, y=10, writer=writer)

    # Move east out of boundary (no treasure in inventory)
    await move_player(player, 1, 0, "east")

    written_text = "".join(call[0][0] for call in writer.write.call_args_list)
    assert "Guards block your path!" not in written_text
    # Player should have moved
    assert player.x == 16
    assert player.y == 10


@pytest.mark.asyncio
async def test_carrying_check_matches_by_tag():
    """Test carrying check matches items by tag."""
    from mudlib.commands.movement import move_player

    zone = Zone(
        name="test",
        width=20,
        height=20,
        terrain=[["." for _ in range(20)] for _ in range(20)],
        boundaries=[
            BoundaryRegion(
                name="vault",
                x_min=10,
                x_max=15,
                y_min=10,
                y_max=15,
                on_exit_check="carrying:valuable",
                on_exit_fail="Guards block your path!",
            ),
        ],
    )

    writer = create_mock_writer()
    player = Player(name="TestPlayer", location=zone, x=15, y=10, writer=writer)

    # Give player an item with "valuable" tag
    Thing(name="gem", location=player, tags=["valuable", "shiny"])

    # Try to move east out of boundary
    await move_player(player, 1, 0, "east")

    written_text = "".join(call[0][0] for call in writer.write.call_args_list)
    assert "Guards block your path!" in written_text
    assert player.x == 15


@pytest.mark.asyncio
async def test_no_boundaries_no_effect():
    """Test that zones with no boundaries work normally."""
    from mudlib.commands.movement import move_player

    zone = Zone(
        name="test",
        width=20,
        height=20,
        terrain=[["." for _ in range(20)] for _ in range(20)],
        boundaries=[],  # No boundaries
    )

    writer = create_mock_writer()
    player = Player(name="TestPlayer", location=zone, x=10, y=10, writer=writer)

    # Movement should work normally
    await move_player(player, 1, 0, "east")
    assert player.x == 11


@pytest.mark.asyncio
async def test_multiple_boundaries_in_zone():
    """Test multiple boundaries in the same zone."""
    from mudlib.commands.movement import move_player

    zone = Zone(
        name="test",
        width=30,
        height=30,
        terrain=[["." for _ in range(30)] for _ in range(30)],
        boundaries=[
            BoundaryRegion(
                name="area1",
                x_min=5,
                x_max=10,
                y_min=5,
                y_max=10,
                on_enter_message="Enter area1",
            ),
            BoundaryRegion(
                name="area2",
                x_min=15,
                x_max=20,
                y_min=15,
                y_max=20,
                on_enter_message="Enter area2",
            ),
        ],
    )

    writer = create_mock_writer()
    player = Player(name="TestPlayer", location=zone, x=4, y=5, writer=writer)

    # Move into area1
    await move_player(player, 1, 0, "east")
    written_text = "".join(call[0][0] for call in writer.write.call_args_list)
    assert "Enter area1" in written_text

    # Move to neutral zone
    writer.write.reset_mock()
    player.x, player.y = 14, 15

    # Move into area2
    await move_player(player, 1, 0, "east")
    written_text = "".join(call[0][0] for call in writer.write.call_args_list)
    assert "Enter area2" in written_text
