import pytest
from shapely.geometry import Polygon

from inire.geometry.collision import CollisionEngine
from inire.geometry.primitives import Port
from inire.router.astar import AStarRouter
from inire.router.cost import CostEvaluator
from inire.router.danger_map import DangerMap
from inire.router.pathfinder import RoutingResult
from inire.utils.validation import validate_routing_result


@pytest.fixture
def basic_evaluator() -> CostEvaluator:
    engine = CollisionEngine(clearance=2.0)
    danger_map = DangerMap(bounds=(0, 0, 100, 100))
    danger_map.precompute([])
    return CostEvaluator(engine, danger_map, bend_penalty=50.0, sbend_penalty=150.0)


def test_astar_straight(basic_evaluator: CostEvaluator) -> None:
    router = AStarRouter(basic_evaluator, snap_size=1.0, straight_lengths=[1.0, 5.0, 25.0])
    start = Port(0, 0, 0)
    target = Port(50, 0, 0)
    path = router.route(start, target, net_width=2.0)

    assert path is not None
    result = RoutingResult(net_id="test", path=path, is_valid=True, collisions=0)
    validation = validate_routing_result(result, [], clearance=2.0, expected_start=start, expected_end=target)

    assert validation["is_valid"], f"Validation failed: {validation.get('reason')}"
    assert validation["connectivity_ok"]
    # Path should be exactly 50um (or slightly more if it did weird things, but here it's straight)
    assert abs(validation["total_length"] - 50.0) < 1e-6


def test_astar_bend(basic_evaluator: CostEvaluator) -> None:
    router = AStarRouter(basic_evaluator, snap_size=1.0, straight_lengths=[1.0, 5.0, 25.0], bend_radii=[10.0])
    start = Port(0, 0, 0)
    # 20um right, 20um up. Needs a 10um bend and a 10um bend.
    target = Port(20, 20, 0)
    path = router.route(start, target, net_width=2.0)

    assert path is not None
    result = RoutingResult(net_id="test", path=path, is_valid=True, collisions=0)
    validation = validate_routing_result(result, [], clearance=2.0, expected_start=start, expected_end=target)

    assert validation["is_valid"], f"Validation failed: {validation.get('reason')}"
    assert validation["connectivity_ok"]


def test_astar_obstacle(basic_evaluator: CostEvaluator) -> None:
    # Add an obstacle in the middle of a straight path
    # Obstacle from x=20 to 40, y=-20 to 20
    obstacle = Polygon([(20, -20), (40, -20), (40, 20), (20, 20)])
    basic_evaluator.collision_engine.add_static_obstacle(obstacle)
    basic_evaluator.danger_map.precompute([obstacle])

    router = AStarRouter(basic_evaluator, snap_size=1.0, straight_lengths=[1.0, 5.0, 25.0], bend_radii=[10.0])
    router.node_limit = 1000000 # Give it more room for detour
    start = Port(0, 0, 0)
    target = Port(60, 0, 0)
    path = router.route(start, target, net_width=2.0)

    assert path is not None
    result = RoutingResult(net_id="test", path=path, is_valid=True, collisions=0)
    validation = validate_routing_result(result, [obstacle], clearance=2.0, expected_start=start, expected_end=target)

    assert validation["is_valid"], f"Validation failed: {validation.get('reason')}"
    # Path should have detoured, so length > 50
    assert validation["total_length"] > 50.0


def test_astar_snap_to_target_lookahead(basic_evaluator: CostEvaluator) -> None:
    router = AStarRouter(basic_evaluator, snap_size=1.0)
    # Target is NOT on 1um grid
    start = Port(0, 0, 0)
    target = Port(10.1, 0, 0)
    path = router.route(start, target, net_width=2.0)

    assert path is not None
    result = RoutingResult(net_id="test", path=path, is_valid=True, collisions=0)
    validation = validate_routing_result(result, [], clearance=2.0, expected_start=start, expected_end=target)

    assert validation["is_valid"], f"Validation failed: {validation.get('reason')}"
