from mudlib.world.terrain import World


def test_world_deterministic_from_seed():
    """Same seed produces identical terrain."""
    world1 = World(seed=42, width=100, height=100)
    world2 = World(seed=42, width=100, height=100)

    for y in range(100):
        for x in range(100):
            assert world1.get_tile(x, y) == world2.get_tile(x, y)


def test_world_different_seeds_produce_different_terrain():
    """Different seeds produce different terrain."""
    world1 = World(seed=42, width=100, height=100)
    world2 = World(seed=99, width=100, height=100)

    different_tiles = 0
    for y in range(100):
        for x in range(100):
            if world1.get_tile(x, y) != world2.get_tile(x, y):
                different_tiles += 1

    # at least 10% should be different
    assert different_tiles > 1000


def test_world_dimensions():
    """World has correct dimensions."""
    world = World(seed=42, width=100, height=50)

    # should not raise for valid coordinates
    world.get_tile(0, 0)
    world.get_tile(99, 49)

    # coordinates wrap instead of raising
    assert world.get_tile(100, 0) == world.get_tile(0, 0)
    assert world.get_tile(0, 50) == world.get_tile(0, 0)
    assert world.get_tile(-1, 0) == world.get_tile(99, 0)
    assert world.get_tile(0, -1) == world.get_tile(0, 49)


def test_get_tile_returns_valid_terrain():
    """get_tile returns valid terrain characters."""
    world = World(seed=42, width=100, height=100)
    valid_terrain = {".", "^", "~", "T", ":"}

    for y in range(100):
        for x in range(100):
            tile = world.get_tile(x, y)
            assert tile in valid_terrain


def test_is_passable_mountains_impassable():
    """Mountains are impassable."""
    world = World(seed=42, width=100, height=100)

    for y in range(100):
        for x in range(100):
            tile = world.get_tile(x, y)
            if tile == "^":
                assert not world.is_passable(x, y)


def test_is_passable_water_impassable():
    """Water is impassable."""
    world = World(seed=42, width=100, height=100)

    for y in range(100):
        for x in range(100):
            tile = world.get_tile(x, y)
            if tile == "~":
                assert not world.is_passable(x, y)


def test_is_passable_grass_passable():
    """Grass is passable."""
    world = World(seed=42, width=100, height=100)

    for y in range(100):
        for x in range(100):
            tile = world.get_tile(x, y)
            if tile == ".":
                assert world.is_passable(x, y)


def test_is_passable_forest_passable():
    """Forest is passable."""
    world = World(seed=42, width=100, height=100)

    for y in range(100):
        for x in range(100):
            tile = world.get_tile(x, y)
            if tile == "T":
                assert world.is_passable(x, y)


def test_is_passable_sand_passable():
    """Sand is passable."""
    world = World(seed=42, width=100, height=100)

    for y in range(100):
        for x in range(100):
            tile = world.get_tile(x, y)
            if tile == ":":
                assert world.is_passable(x, y)


def test_get_viewport_dimensions():
    """get_viewport returns correct dimensions."""
    world = World(seed=42, width=100, height=100)
    viewport = world.get_viewport(50, 50, width=10, height=8)

    assert len(viewport) == 8
    assert len(viewport[0]) == 10


def test_get_viewport_centers_correctly():
    """get_viewport centers on given coordinates."""
    world = World(seed=42, width=100, height=100)
    viewport = world.get_viewport(50, 50, width=5, height=5)

    # center tile should be at position (2, 2) in viewport
    center_tile = viewport[2][2]
    assert center_tile == world.get_tile(50, 50)


def test_get_viewport_wraps_at_edges():
    """Viewport wraps around world boundaries."""
    world = World(seed=42, width=100, height=100)

    # near top-left corner - viewport should wrap to bottom/right
    viewport = world.get_viewport(0, 0, width=5, height=5)
    assert len(viewport) == 5
    assert len(viewport[0]) == 5
    # top-left of viewport wraps to bottom-right of world
    assert viewport[0][0] == world.get_tile(98, 98)

    # near bottom-right corner - viewport should wrap to top/left
    viewport = world.get_viewport(99, 99, width=5, height=5)
    assert len(viewport) == 5
    assert len(viewport[0]) == 5
    # bottom-right of viewport wraps to top-left of world
    assert viewport[4][4] == world.get_tile(1, 1)


def test_terrain_distribution_reasonable():
    """Terrain has reasonable distribution (not all one type)."""
    world = World(seed=42, width=100, height=100)
    terrain_counts = {".": 0, "^": 0, "~": 0, "T": 0, ":": 0}

    for y in range(100):
        for x in range(100):
            tile = world.get_tile(x, y)
            terrain_counts[tile] += 1

    # should have at least 3 different terrain types
    types_present = sum(1 for count in terrain_counts.values() if count > 0)
    assert types_present >= 3

    # no single type should dominate completely (> 95%)
    total = sum(terrain_counts.values())
    for terrain_type, count in terrain_counts.items():
        assert count < 0.95 * total, f"{terrain_type} dominates with {count}/{total}"


def test_rivers_exist():
    """Terrain has some water tiles (rivers/lakes)."""
    world = World(seed=42, width=100, height=100)
    water_count = 0

    for y in range(100):
        for x in range(100):
            if world.get_tile(x, y) == "~":
                water_count += 1

    # should have at least some water
    assert water_count > 0


def test_terrain_tiles_seamlessly():
    """Terrain at opposite edges matches for seamless wrapping."""
    world = World(seed=42, width=100, height=100)

    # check that a strip along the right edge is continuous with the left edge
    # by verifying the elevation-derived terrain doesn't have a hard seam.
    # the noise is tileable, so adjacent tiles across the boundary should be
    # from the same smooth noise field (not identical, but not a cliff).
    # we verify the wrap helper itself works correctly:
    for y in range(100):
        assert world.get_tile(-1, y) == world.get_tile(99, y)
        assert world.get_tile(100, y) == world.get_tile(0, y)
    for x in range(100):
        assert world.get_tile(x, -1) == world.get_tile(x, 99)
        assert world.get_tile(x, 100) == world.get_tile(x, 0)


def test_large_world_generation():
    """Can generate a 1000x1000 world without errors."""
    world = World(seed=42, width=1000, height=1000)

    # spot check some tiles
    assert world.get_tile(0, 0) in {".", "^", "~", "T", ":"}
    assert world.get_tile(500, 500) in {".", "^", "~", "T", ":"}
    assert world.get_tile(999, 999) in {".", "^", "~", "T", ":"}


# --- terrain caching ---


def test_world_saves_cache(tmp_path):
    """World saves terrain cache when cache_dir is provided."""
    world = World(seed=42, width=100, height=100, cache_dir=tmp_path)
    cache_file = tmp_path / "terrain_42_100x100.bin"
    assert cache_file.exists()
    assert cache_file.stat().st_size == 100 * 100  # 1 byte per tile
    assert not world.cached  # first run generates, doesn't load from cache


def test_world_loads_from_cache(tmp_path):
    """World loads terrain from cache on second creation."""
    world1 = World(seed=42, width=100, height=100, cache_dir=tmp_path)
    world2 = World(seed=42, width=100, height=100, cache_dir=tmp_path)

    for y in range(100):
        for x in range(100):
            assert world1.get_tile(x, y) == world2.get_tile(x, y)

    assert world2.cached


def test_world_cache_different_params(tmp_path):
    """Different seed/size doesn't use wrong cache."""
    World(seed=42, width=100, height=100, cache_dir=tmp_path)
    world2 = World(seed=99, width=100, height=100, cache_dir=tmp_path)
    assert not world2.cached  # different seed, must generate


def test_world_no_cache_by_default():
    """World doesn't cache when no cache_dir provided."""
    world = World(seed=42, width=50, height=50)
    assert not world.cached
