"""Tests for the Zone class."""

from mudlib.object import Object
from mudlib.zone import Zone

# --- construction ---


def test_zone_basic_creation():
    """Zone created with name, dimensions, and terrain."""
    terrain = [["." for _ in range(10)] for _ in range(5)]
    zone = Zone(name="test", width=10, height=5, terrain=terrain)
    assert zone.name == "test"
    assert zone.width == 10
    assert zone.height == 5
    assert zone.toroidal is True  # default
    assert zone.location is None  # zones are top-level


def test_zone_bounded():
    """Zone can be created with toroidal=False (bounded)."""
    terrain = [["." for _ in range(5)] for _ in range(5)]
    zone = Zone(name="room", width=5, height=5, terrain=terrain, toroidal=False)
    assert zone.toroidal is False


def test_zone_default_impassable():
    """Zone has default impassable set of mountain and water."""
    terrain = [["."]]
    zone = Zone(name="test", width=1, height=1, terrain=terrain)
    assert "^" in zone.impassable
    assert "~" in zone.impassable


def test_zone_default_spawn_point():
    """Zone has spawn_x=0, spawn_y=0 by default."""
    zone = Zone(name="test")
    assert zone.spawn_x == 0
    assert zone.spawn_y == 0


def test_zone_custom_spawn_point():
    """Zone can be created with custom spawn point."""
    zone = Zone(name="test", spawn_x=3, spawn_y=4)
    assert zone.spawn_x == 3
    assert zone.spawn_y == 4


# --- can_accept ---


def test_zone_can_accept_returns_true():
    """Zones accept everything."""
    terrain = [["."]]
    zone = Zone(name="test", width=1, height=1, terrain=terrain)
    obj = Object(name="rock")
    assert zone.can_accept(obj) is True


# --- wrap ---


def test_wrap_toroidal():
    """Toroidal zone wraps coordinates around both axes."""
    terrain = [["." for _ in range(100)] for _ in range(100)]
    zone = Zone(name="overworld", width=100, height=100, terrain=terrain)
    assert zone.wrap(105, 205) == (5, 5)
    assert zone.wrap(-1, -1) == (99, 99)
    assert zone.wrap(50, 50) == (50, 50)


def test_wrap_bounded():
    """Bounded zone clamps coordinates to edges."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    zone = Zone(name="room", width=10, height=10, terrain=terrain, toroidal=False)
    assert zone.wrap(15, 15) == (9, 9)
    assert zone.wrap(-5, -3) == (0, 0)
    assert zone.wrap(5, 5) == (5, 5)


# --- get_tile ---


def test_get_tile():
    """get_tile returns the terrain character at a position."""
    terrain = [
        [".", "T", "^"],
        ["~", ".", ":"],
    ]
    zone = Zone(name="test", width=3, height=2, terrain=terrain)
    assert zone.get_tile(0, 0) == "."
    assert zone.get_tile(1, 0) == "T"
    assert zone.get_tile(2, 0) == "^"
    assert zone.get_tile(0, 1) == "~"
    assert zone.get_tile(2, 1) == ":"


def test_get_tile_wraps():
    """get_tile wraps coordinates on toroidal zone."""
    terrain = [["." for _ in range(5)] for _ in range(5)]
    terrain[0][0] = "T"
    zone = Zone(name="test", width=5, height=5, terrain=terrain)
    assert zone.get_tile(5, 5) == "T"  # wraps to (0, 0)


# --- is_passable ---


def test_is_passable_grass():
    """Grass tiles are passable."""
    terrain = [["."]]
    zone = Zone(name="test", width=1, height=1, terrain=terrain)
    assert zone.is_passable(0, 0) is True


def test_is_passable_mountain():
    """Mountain tiles are impassable."""
    terrain = [["^"]]
    zone = Zone(name="test", width=1, height=1, terrain=terrain)
    assert zone.is_passable(0, 0) is False


def test_is_passable_water():
    """Water tiles are impassable."""
    terrain = [["~"]]
    zone = Zone(name="test", width=1, height=1, terrain=terrain)
    assert zone.is_passable(0, 0) is False


def test_is_passable_custom_impassable():
    """Zone can have custom impassable set."""
    terrain = [[".", "X"]]
    zone = Zone(name="test", width=2, height=1, terrain=terrain, impassable={"X"})
    assert zone.is_passable(0, 0) is True
    assert zone.is_passable(1, 0) is False


# --- get_viewport ---


def test_get_viewport_centered():
    """Viewport returns terrain slice centered on given position."""
    terrain = [["." for _ in range(21)] for _ in range(11)]
    terrain[5][10] = "@"  # center
    zone = Zone(name="test", width=21, height=11, terrain=terrain)
    vp = zone.get_viewport(10, 5, 5, 3)
    # 5 wide, 3 tall, centered on (10, 5)
    # Should be columns 8-12, rows 4-6
    assert len(vp) == 3
    assert len(vp[0]) == 5
    assert vp[1][2] == "@"  # center of viewport


def test_get_viewport_wraps_toroidal():
    """Viewport wraps around edges of toroidal zone."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    terrain[0][0] = "T"  # top-left corner
    zone = Zone(name="test", width=10, height=10, terrain=terrain)
    # Viewport centered at (0, 0), size 5x5
    vp = zone.get_viewport(0, 0, 5, 5)
    # Center of viewport is (2, 2), should be "T"
    assert vp[2][2] == "T"
    # Top-left of viewport wraps to bottom-right of zone
    assert len(vp) == 5
    assert len(vp[0]) == 5


def test_get_viewport_full_size():
    """Viewport with standard 21x11 size works."""
    terrain = [["." for _ in range(100)] for _ in range(100)]
    zone = Zone(name="test", width=100, height=100, terrain=terrain)
    vp = zone.get_viewport(50, 50, 21, 11)
    assert len(vp) == 11
    assert len(vp[0]) == 21


# --- contents_at ---


def test_contents_at_empty():
    """contents_at returns empty list when nothing at position."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    zone = Zone(name="test", width=10, height=10, terrain=terrain)
    assert zone.contents_at(5, 5) == []


def test_contents_at_finds_objects():
    """contents_at returns objects at the given coordinates."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    zone = Zone(name="test", width=10, height=10, terrain=terrain)
    obj = Object(name="rock", location=zone, x=3, y=7)
    result = zone.contents_at(3, 7)
    assert obj in result


def test_contents_at_excludes_other_positions():
    """contents_at only returns objects at exact coordinates."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    zone = Zone(name="test", width=10, height=10, terrain=terrain)
    Object(name="rock", location=zone, x=3, y=7)
    Object(name="tree", location=zone, x=5, y=5)
    result = zone.contents_at(3, 7)
    assert len(result) == 1
    assert result[0].name == "rock"


def test_contents_at_multiple():
    """contents_at returns all objects at the same position."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    zone = Zone(name="test", width=10, height=10, terrain=terrain)
    rock = Object(name="rock", location=zone, x=5, y=5)
    gem = Object(name="gem", location=zone, x=5, y=5)
    result = zone.contents_at(5, 5)
    assert rock in result
    assert gem in result
    assert len(result) == 2


# --- contents_near ---


def test_contents_near_empty():
    """contents_near returns empty list when nothing nearby."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    zone = Zone(name="test", width=10, height=10, terrain=terrain)
    assert zone.contents_near(5, 5, 3) == []


def test_contents_near_includes_exact_position():
    """contents_near includes objects at the exact coordinates."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    zone = Zone(name="test", width=10, height=10, terrain=terrain)
    obj = Object(name="rock", location=zone, x=5, y=5)
    result = zone.contents_near(5, 5, 3)
    assert obj in result


def test_contents_near_within_range():
    """contents_near returns objects within Manhattan distance range."""
    terrain = [["." for _ in range(20)] for _ in range(20)]
    zone = Zone(name="test", width=20, height=20, terrain=terrain)
    center = Object(name="center", location=zone, x=10, y=10)
    nearby1 = Object(name="nearby1", location=zone, x=11, y=10)  # distance 1
    nearby2 = Object(name="nearby2", location=zone, x=10, y=12)  # distance 2
    nearby3 = Object(name="nearby3", location=zone, x=12, y=12)  # distance 4
    far = Object(name="far", location=zone, x=15, y=15)  # distance 10

    result = zone.contents_near(10, 10, 3)
    assert center in result
    assert nearby1 in result
    assert nearby2 in result
    assert nearby3 not in result  # distance 4 > range 3
    assert far not in result


def test_contents_near_wrapping_toroidal():
    """contents_near uses wrapping-aware distance on toroidal zones."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    zone = Zone(name="test", width=10, height=10, terrain=terrain, toroidal=True)
    # Object at (1, 1), query at (9, 9)
    # Without wrapping: dx=8, dy=8
    # With wrapping: dx=min(8, 10-8)=2, dy=min(8, 10-8)=2, total=4
    obj = Object(name="wrapped", location=zone, x=1, y=1)
    result = zone.contents_near(9, 9, 5)
    assert obj in result  # within range due to wrapping

    result_tight = zone.contents_near(9, 9, 3)
    assert obj not in result_tight  # outside tighter range


def test_contents_near_no_wrapping_bounded():
    """contents_near uses straight distance on bounded zones."""
    terrain = [["." for _ in range(10)] for _ in range(10)]
    zone = Zone(name="test", width=10, height=10, terrain=terrain, toroidal=False)
    # Object at (1, 1), query at (9, 9)
    # Straight distance: dx=8, dy=8, total=16
    obj = Object(name="far", location=zone, x=1, y=1)
    result = zone.contents_near(9, 9, 5)
    assert obj not in result  # too far on bounded zone


def test_contents_near_multiple():
    """contents_near returns all objects within range."""
    terrain = [["." for _ in range(20)] for _ in range(20)]
    zone = Zone(name="test", width=20, height=20, terrain=terrain)
    obj1 = Object(name="obj1", location=zone, x=10, y=10)
    obj2 = Object(name="obj2", location=zone, x=11, y=10)
    obj3 = Object(name="obj3", location=zone, x=10, y=11)
    Object(name="far", location=zone, x=20, y=20)  # far away

    result = zone.contents_near(10, 10, 2)
    assert len(result) == 3
    assert obj1 in result
    assert obj2 in result
    assert obj3 in result
