from __future__ import annotations

from typing import TYPE_CHECKING, Any
import numpy

if TYPE_CHECKING:
    from shapely.geometry import Polygon
    from inire.geometry.primitives import Port
    from inire.router.pathfinder import RoutingResult


def validate_routing_result(
    result: RoutingResult,
    static_obstacles: list[Polygon],
    clearance: float,
    expected_start: Port | None = None,
    expected_end: Port | None = None,
) -> dict[str, Any]:
    """
    Perform a high-precision validation of a routed path.

    Args:
        result: The routing result to validate.
        static_obstacles: List of static obstacle geometries.
        clearance: Required minimum distance.
        expected_start: Optional expected start port.
        expected_end: Optional expected end port.

    Returns:
        A dictionary with validation results.
    """
    _ = expected_start
    if not result.path:
        return {"is_valid": False, "reason": "No path found"}

    obstacle_collision_geoms = []
    self_intersection_geoms = []
    connectivity_errors = []

    # 1. Connectivity Check
    total_length = 0.0
    for comp in result.path:
        total_length += comp.length

    # Boundary check
    if expected_end:
        last_port = result.path[-1].end_port
        dist_to_end = numpy.sqrt((last_port.x - expected_end.x)**2 + (last_port.y - expected_end.y)**2)
        if dist_to_end > 0.005:
            connectivity_errors.append(f"Final port position mismatch: {dist_to_end*1000:.2f}nm")
        if abs(last_port.orientation - expected_end.orientation) > 0.1:
            connectivity_errors.append(f"Final port orientation mismatch: {last_port.orientation} vs {expected_end.orientation}")

    # 2. Geometry Buffering
    dilation_half = clearance / 2.0
    dilation_full = clearance

    dilated_for_self = []

    for comp in result.path:
        for poly in comp.geometry:
            # Check against obstacles
            d_full = poly.buffer(dilation_full)
            for obs in static_obstacles:
                if d_full.intersects(obs):
                    intersection = d_full.intersection(obs)
                    if intersection.area > 1e-9:
                        obstacle_collision_geoms.append(intersection)

            # Save for self-intersection check
            dilated_for_self.append(poly.buffer(dilation_half))

    # 3. Self-intersection
    for i, seg_i in enumerate(dilated_for_self):
        for j, seg_j in enumerate(dilated_for_self):
            if j > i + 1 and seg_i.intersects(seg_j): # Non-adjacent
                overlap = seg_i.intersection(seg_j)
                if overlap.area > 1e-6:
                    self_intersection_geoms.append((i, j, overlap))

    is_valid = (len(obstacle_collision_geoms) == 0 and
                len(self_intersection_geoms) == 0 and
                len(connectivity_errors) == 0)

    reasons = []
    if obstacle_collision_geoms:
        reasons.append(f"Found {len(obstacle_collision_geoms)} obstacle collisions.")
    if self_intersection_geoms:
        # report which indices
        idx_str = ", ".join([f"{i}-{j}" for i, j, _ in self_intersection_geoms[:5]])
        reasons.append(f"Found {len(self_intersection_geoms)} self-intersections (e.g. {idx_str}).")
    if connectivity_errors:
        reasons.extend(connectivity_errors)

    return {
        "is_valid": is_valid,
        "reason": " ".join(reasons),
        "obstacle_collisions": obstacle_collision_geoms,
        "self_intersections": self_intersection_geoms,
        "total_length": total_length,
        "connectivity_ok": len(connectivity_errors) == 0,
    }
