import pytest

from inire.geometry.components import Bend90, SBend, Straight
from inire.geometry.primitives import Port, rotate_port, translate_port


def test_straight_generation() -> None:
    start = Port(0, 0, 0)
    length = 10.0
    width = 2.0
    result = Straight.generate(start, length, width, snap_size=1.0)

    assert result.end_port.x == 10.0
    assert result.end_port.y == 0.0
    assert result.end_port.orientation == 0.0
    assert len(result.geometry) == 1

    # Bounds of the polygon
    minx, miny, maxx, maxy = result.geometry[0].bounds
    assert minx == 0.0
    assert maxx == 10.0
    assert miny == -1.0
    assert maxy == 1.0


def test_bend90_generation() -> None:
    start = Port(0, 0, 0)
    radius = 10.0
    width = 2.0

    # CW bend
    result_cw = Bend90.generate(start, radius, width, direction="CW", snap_size=1.0)
    assert result_cw.end_port.x == 10.0
    assert result_cw.end_port.y == -10.0
    assert result_cw.end_port.orientation == 270.0

    # CCW bend
    result_ccw = Bend90.generate(start, radius, width, direction="CCW", snap_size=1.0)
    assert result_ccw.end_port.x == 10.0
    assert result_ccw.end_port.y == 10.0
    assert result_ccw.end_port.orientation == 90.0


def test_sbend_generation() -> None:
    start = Port(0, 0, 0)
    offset = 5.0
    radius = 10.0
    width = 2.0

    result = SBend.generate(start, offset, radius, width, snap_size=1.0)
    assert result.end_port.y == 5.0
    assert result.end_port.orientation == 0.0
    assert len(result.geometry) == 2 # Optimization: returns individual arcs

    # Verify failure for large offset
    with pytest.raises(ValueError, match=r"SBend offset .* must be less than 2\*radius"):
        SBend.generate(start, 25.0, 10.0, 2.0)


def test_bend_collision_models() -> None:
    start = Port(0, 0, 0)
    radius = 10.0
    width = 2.0

    # 1. BBox model
    res_bbox = Bend90.generate(start, radius, width, direction="CCW", collision_type="bbox", snap_size=1.0)
    # Arc CCW R=10 from (0,0,0) ends at (10,10,90).
    # Waveguide width is 2.0, so bbox will be slightly larger than (0,0,10,10)
    minx, miny, maxx, maxy = res_bbox.geometry[0].bounds
    assert minx <= 0.0 + 1e-6
    assert maxx >= 10.0 - 1e-6
    assert miny <= 0.0 + 1e-6
    assert maxy >= 10.0 - 1e-6

    # 2. Clipped BBox model
    res_clipped = Bend90.generate(start, radius, width, direction="CCW", collision_type="clipped_bbox", clip_margin=1.0, snap_size=1.0)
    # Area should be less than full bbox
    assert res_clipped.geometry[0].area < res_bbox.geometry[0].area


def test_sbend_collision_models() -> None:
    start = Port(0, 0, 0)
    offset = 5.0
    radius = 10.0
    width = 2.0

    res_bbox = SBend.generate(start, offset, radius, width, collision_type="bbox", snap_size=1.0)
    # Geometry should be a list of individual bbox polygons for each arc
    assert len(res_bbox.geometry) == 2

    res_arc = SBend.generate(start, offset, radius, width, collision_type="arc", snap_size=1.0)
    area_bbox = sum(p.area for p in res_bbox.geometry)
    area_arc = sum(p.area for p in res_arc.geometry)
    assert area_bbox > area_arc


def test_sbend_continuity() -> None:
    # Verify SBend endpoints and continuity math
    start = Port(10, 20, 90)  # Starting facing up
    offset = 4.0
    radius = 20.0
    width = 1.0

    # We use snap_size=1.0 so that (10-offset) = 6.0 is EXACTLY hit.
    res = SBend.generate(start, offset, radius, width, snap_size=1.0)

    # Target orientation should be same as start
    assert abs(res.end_port.orientation - 90.0) < 1e-6

    # For a port at 90 deg, +offset is a shift in -x direction
    assert abs(res.end_port.x - (10.0 - offset)) < 1e-6

    # Geometry should be a list of valid polygons
    assert len(res.geometry) == 2
    for p in res.geometry:
        assert p.is_valid


def test_arc_sagitta_precision() -> None:
    # Verify that requested sagitta actually controls segment count
    start = Port(0, 0, 0)
    radius = 100.0  # Large radius to make sagitta significant
    width = 2.0

    # Coarse: 1um sagitta
    res_coarse = Bend90.generate(start, radius, width, direction="CCW", sagitta=1.0)
    # Fine: 0.01um (10nm) sagitta
    res_fine = Bend90.generate(start, radius, width, direction="CCW", sagitta=0.01)

    # Number of segments should be significantly higher for fine
    # Exterior points = (segments + 1) * 2
    pts_coarse = len(res_coarse.geometry[0].exterior.coords)
    pts_fine = len(res_fine.geometry[0].exterior.coords)

    assert pts_fine > pts_coarse * 2


def test_component_transform_invariance() -> None:
    # Verify that generating at (0,0) then transforming
    # is same as generating at the transformed port.
    start0 = Port(0, 0, 0)
    radius = 10.0
    width = 2.0

    res0 = Bend90.generate(start0, radius, width, direction="CCW", snap_size=1.0)

    # Transform: Translate (10, 10) then Rotate 90
    dx, dy = 10.0, 5.0
    angle = 90.0

    # 1. Transform the generated geometry
    p_end_transformed = rotate_port(translate_port(res0.end_port, dx, dy), angle)

    # 2. Generate at transformed start
    start_transformed = rotate_port(translate_port(start0, dx, dy), angle)
    res_transformed = Bend90.generate(start_transformed, radius, width, direction="CCW", snap_size=1.0)

    assert abs(res_transformed.end_port.x - p_end_transformed.x) < 1e-6
    assert abs(res_transformed.end_port.y - p_end_transformed.y) < 1e-6
    assert abs(res_transformed.end_port.orientation - p_end_transformed.orientation) < 1e-6
