#!/usr/bin/env python3
"""Unit tests for Grid.apply_threshold and Grid.apply_ranges (#199)

Tests the Grid methods for applying HeightMap data to walkable/transparent properties.
"""

import sys
import mcrfpy


def test_apply_threshold_walkable():
    """apply_threshold sets walkable property correctly"""
    grid = mcrfpy.Grid(grid_size=(10, 10))
    hmap = mcrfpy.HeightMap((10, 10), fill=0.5)

    # All cells start with default walkable
    grid.apply_threshold(hmap, range=(0.0, 1.0), walkable=True)

    # Check a few cells
    assert grid.at((5, 5)).walkable == True
    assert grid.at((0, 0)).walkable == True
    assert grid.at((9, 9)).walkable == True
    print("PASS: test_apply_threshold_walkable")


def test_apply_threshold_transparent():
    """apply_threshold sets transparent property correctly"""
    grid = mcrfpy.Grid(grid_size=(10, 10))
    hmap = mcrfpy.HeightMap((10, 10), fill=0.5)

    grid.apply_threshold(hmap, range=(0.0, 1.0), transparent=False)

    assert grid.at((5, 5)).transparent == False
    print("PASS: test_apply_threshold_transparent")


def test_apply_threshold_both():
    """apply_threshold sets both walkable and transparent"""
    grid = mcrfpy.Grid(grid_size=(10, 10))
    hmap = mcrfpy.HeightMap((10, 10), fill=0.5)

    grid.apply_threshold(hmap, range=(0.0, 1.0), walkable=True, transparent=True)

    point = grid.at((5, 5))
    assert point.walkable == True
    assert point.transparent == True
    print("PASS: test_apply_threshold_both")


def test_apply_threshold_out_of_range():
    """apply_threshold doesn't affect cells outside range"""
    grid = mcrfpy.Grid(grid_size=(10, 10))
    hmap = mcrfpy.HeightMap((10, 10), fill=0.5)

    # Set initial state
    grid.at((5, 5)).walkable = False
    grid.at((5, 5)).transparent = False

    # Apply threshold with range that excludes 0.5
    grid.apply_threshold(hmap, range=(0.0, 0.4), walkable=True, transparent=True)

    # Cell should remain unchanged
    assert grid.at((5, 5)).walkable == False
    assert grid.at((5, 5)).transparent == False
    print("PASS: test_apply_threshold_out_of_range")


def test_apply_threshold_returns_self():
    """apply_threshold returns the GridData for chaining

    Current contract (#313/#361): the apply_* methods live on GridData, and the
    Grid view forwards to it. They return the GridData ("self"), not the view.
    """
    grid = mcrfpy.Grid(grid_size=(10, 10))
    hmap = mcrfpy.HeightMap((10, 10), fill=0.5)

    result = grid.apply_threshold(hmap, range=(0.0, 1.0), walkable=True)
    assert result is grid.grid_data, "apply_threshold should return the GridData (self)"
    print("PASS: test_apply_threshold_returns_self")


def test_apply_threshold_size_mismatch():
    """apply_threshold raises ValueError for size mismatch"""
    grid = mcrfpy.Grid(grid_size=(10, 10))
    hmap = mcrfpy.HeightMap((20, 20), fill=0.5)

    try:
        grid.apply_threshold(hmap, range=(0.0, 1.0), walkable=True)
        print("FAIL: test_apply_threshold_size_mismatch - should have raised ValueError")
        sys.exit(1)
    except ValueError as e:
        assert "size" in str(e).lower()

    print("PASS: test_apply_threshold_size_mismatch")


def test_apply_threshold_invalid_source():
    """apply_threshold raises TypeError for non-HeightMap source"""
    grid = mcrfpy.Grid(grid_size=(10, 10))

    try:
        grid.apply_threshold("not a heightmap", range=(0.0, 1.0), walkable=True)
        print("FAIL: test_apply_threshold_invalid_source - should have raised TypeError")
        sys.exit(1)
    except TypeError:
        pass

    print("PASS: test_apply_threshold_invalid_source")


def test_apply_threshold_none_values():
    """apply_threshold with None values leaves properties unchanged"""
    grid = mcrfpy.Grid(grid_size=(10, 10))
    hmap = mcrfpy.HeightMap((10, 10), fill=0.5)

    # Set initial state
    grid.at((5, 5)).walkable = True
    grid.at((5, 5)).transparent = False

    # Apply with only walkable=False, transparent should stay unchanged
    grid.apply_threshold(hmap, range=(0.0, 1.0), walkable=False)

    assert grid.at((5, 5)).walkable == False
    assert grid.at((5, 5)).transparent == False  # Unchanged
    print("PASS: test_apply_threshold_none_values")


def test_apply_ranges_basic():
    """apply_ranges applies multiple ranges correctly"""
    grid = mcrfpy.Grid(grid_size=(10, 10))
    hmap = mcrfpy.HeightMap((10, 10), fill=0.5)

    # Apply a range that covers 0.5
    grid.apply_ranges(hmap, [
        ((0.4, 0.6), {"walkable": True, "transparent": True}),
    ])

    assert grid.at((5, 5)).walkable == True
    assert grid.at((5, 5)).transparent == True
    print("PASS: test_apply_ranges_basic")


def test_apply_ranges_first_match_wins():
    """apply_ranges uses first matching range"""
    grid = mcrfpy.Grid(grid_size=(10, 10))
    hmap = mcrfpy.HeightMap((10, 10), fill=0.5)

    # Both ranges cover 0.5, first should win
    grid.apply_ranges(hmap, [
        ((0.0, 0.6), {"walkable": True}),
        ((0.4, 1.0), {"walkable": False}),
    ])

    assert grid.at((5, 5)).walkable == True  # First match wins
    print("PASS: test_apply_ranges_first_match_wins")


def test_apply_ranges_returns_self():
    """apply_ranges returns self for chaining"""
    grid = mcrfpy.Grid(grid_size=(10, 10))
    hmap = mcrfpy.HeightMap((10, 10), fill=0.5)

    result = grid.apply_ranges(hmap, [
        ((0.0, 1.0), {"walkable": True}),
    ])
    assert result is grid.grid_data, "apply_ranges should return the GridData (self)"
    print("PASS: test_apply_ranges_returns_self")


def test_apply_ranges_size_mismatch():
    """apply_ranges raises ValueError for size mismatch"""
    grid = mcrfpy.Grid(grid_size=(10, 10))
    hmap = mcrfpy.HeightMap((5, 5), fill=0.5)

    try:
        grid.apply_ranges(hmap, [
            ((0.0, 1.0), {"walkable": True}),
        ])
        print("FAIL: test_apply_ranges_size_mismatch - should have raised ValueError")
        sys.exit(1)
    except ValueError as e:
        assert "size" in str(e).lower()

    print("PASS: test_apply_ranges_size_mismatch")


def test_apply_ranges_empty_list():
    """apply_ranges with empty list doesn't change anything"""
    grid = mcrfpy.Grid(grid_size=(10, 10))
    hmap = mcrfpy.HeightMap((10, 10), fill=0.5)

    grid.at((5, 5)).walkable = True
    grid.at((5, 5)).transparent = False

    grid.apply_ranges(hmap, [])

    # Should remain unchanged
    assert grid.at((5, 5)).walkable == True
    assert grid.at((5, 5)).transparent == False
    print("PASS: test_apply_ranges_empty_list")


def test_apply_ranges_no_match():
    """apply_ranges leaves cells unchanged when no range matches"""
    grid = mcrfpy.Grid(grid_size=(10, 10))
    hmap = mcrfpy.HeightMap((10, 10), fill=0.5)

    grid.at((5, 5)).walkable = True
    grid.at((5, 5)).transparent = True

    # Ranges that don't include 0.5
    grid.apply_ranges(hmap, [
        ((0.0, 0.4), {"walkable": False}),
        ((0.6, 1.0), {"transparent": False}),
    ])

    # Should remain unchanged
    assert grid.at((5, 5)).walkable == True
    assert grid.at((5, 5)).transparent == True
    print("PASS: test_apply_ranges_no_match")


def test_apply_ranges_invalid_format():
    """apply_ranges raises TypeError for invalid format"""
    grid = mcrfpy.Grid(grid_size=(10, 10))
    hmap = mcrfpy.HeightMap((10, 10), fill=0.5)

    # Invalid: not a list
    try:
        grid.apply_ranges(hmap, "not a list")
        print("FAIL: should have raised TypeError for non-list")
        sys.exit(1)
    except TypeError:
        pass

    # Invalid: entry not a tuple
    try:
        grid.apply_ranges(hmap, ["not a tuple"])
        print("FAIL: should have raised TypeError for non-tuple entry")
        sys.exit(1)
    except TypeError:
        pass

    # Invalid: range not a tuple
    try:
        grid.apply_ranges(hmap, [
            ([0.0, 1.0], {"walkable": True}),  # list instead of tuple for range
        ])
        print("FAIL: should have raised TypeError for non-tuple range")
        sys.exit(1)
    except TypeError:
        pass

    # Invalid: props not a dict
    try:
        grid.apply_ranges(hmap, [
            ((0.0, 1.0), "not a dict"),
        ])
        print("FAIL: should have raised TypeError for non-dict props")
        sys.exit(1)
    except TypeError:
        pass

    print("PASS: test_apply_ranges_invalid_format")


def test_chaining():
    """Methods can be chained together"""
    grid = mcrfpy.Grid(grid_size=(10, 10))
    hmap = mcrfpy.HeightMap((10, 10))

    # Chain multiple operations
    hmap.fill(0.5)

    result = (grid
              .apply_threshold(hmap, range=(0.0, 0.4), walkable=False)
              .apply_threshold(hmap, range=(0.6, 1.0), transparent=False)
              .apply_ranges(hmap, [
                  ((0.4, 0.6), {"walkable": True, "transparent": True}),
              ]))

    assert result is grid.grid_data
    print("PASS: test_chaining")


def run_all_tests():
    """Run all tests"""
    print("Running Grid apply method tests...")
    print()

    test_apply_threshold_walkable()
    test_apply_threshold_transparent()
    test_apply_threshold_both()
    test_apply_threshold_out_of_range()
    test_apply_threshold_returns_self()
    test_apply_threshold_size_mismatch()
    test_apply_threshold_invalid_source()
    test_apply_threshold_none_values()
    test_apply_ranges_basic()
    test_apply_ranges_first_match_wins()
    test_apply_ranges_returns_self()
    test_apply_ranges_size_mismatch()
    test_apply_ranges_empty_list()
    test_apply_ranges_no_match()
    test_apply_ranges_invalid_format()
    test_chaining()

    print()
    print("All Grid apply method tests PASSED!")


# Run tests directly
run_all_tests()
sys.exit(0)
