"""Tests for terrain sheet bounds operations."""

import numpy as np
import pytest
from numpy.testing import assert_allclose

from gri_utils.terrain import (
    SheetBounds,
    bounds_overlap,
    get_sheet_bounds,
    lla_to_xyz_sheet,
    point_within_bounds,
    ray_intersects_bounds,
)


def test_get_sheet_bounds_basic() -> None:
    """Test basic bounds computation."""
    lats = np.linspace(39.0, 40.0, 10)
    lons = np.linspace(-105.0, -104.0, 10)
    alts = np.zeros((10, 10))

    sheet = lla_to_xyz_sheet(lats, lons, alts)
    bounds = get_sheet_bounds(sheet)

    assert isinstance(bounds, SheetBounds)
    assert bounds.center_xyz.shape == (3,)
    assert bounds.min_radius > 6.3e6
    assert bounds.max_radius < 6.4e6
    assert bounds.min_radius <= bounds.avg_radius <= bounds.max_radius
    assert_allclose(bounds.lat_bounds, (39.0, 40.0), rtol=0.01)
    assert_allclose(bounds.lon_bounds, (-105.0, -104.0), rtol=0.01)


def test_get_sheet_bounds_with_terrain() -> None:
    """Test bounds with variable terrain altitude."""
    lats = np.linspace(39.0, 40.0, 10)
    lons = np.linspace(-105.0, -104.0, 10)
    # Create terrain with 2000m variation
    alts = np.zeros((10, 10))
    alts[5, 5] = 2000.0  # Peak

    sheet = lla_to_xyz_sheet(lats, lons, alts)
    bounds = get_sheet_bounds(sheet)

    # Max radius should be higher than min
    assert bounds.max_radius - bounds.min_radius > 1900  # Close to 2000m difference


def test_ray_intersects_bounds_hit() -> None:
    """Test ray that should hit sheet bounds."""
    lats = np.linspace(39.0, 40.0, 10)
    lons = np.linspace(-105.0, -104.0, 10)
    alts = np.zeros((10, 10))

    sheet = lla_to_xyz_sheet(lats, lons, alts)
    bounds = get_sheet_bounds(sheet)

    # Ray from space pointing toward sheet center
    origin = bounds.center_xyz * 2  # 2x Earth radius out
    direction = -bounds.center_xyz  # Toward Earth center

    could_hit, approx_point = ray_intersects_bounds(origin, direction, bounds)

    assert could_hit
    assert approx_point is not None
    assert approx_point.shape == (3,)


def test_ray_intersects_bounds_miss() -> None:
    """Test ray that should miss sheet bounds."""
    lats = np.linspace(39.0, 40.0, 10)
    lons = np.linspace(-105.0, -104.0, 10)
    alts = np.zeros((10, 10))

    sheet = lla_to_xyz_sheet(lats, lons, alts)
    bounds = get_sheet_bounds(sheet)

    # Ray parallel to Earth surface, far from sheet
    origin = np.array([0.0, 0.0, 7e6])  # North pole, high altitude
    direction = np.array([1.0, 0.0, 0.0])  # East

    could_hit, approx_point = ray_intersects_bounds(origin, direction, bounds)

    assert not could_hit
    assert approx_point is None


def test_ray_intersects_bounds_ascending() -> None:
    """Test ray ascending away from terrain."""
    lats = np.linspace(39.0, 40.0, 10)
    lons = np.linspace(-105.0, -104.0, 10)
    alts = np.zeros((10, 10))

    sheet = lla_to_xyz_sheet(lats, lons, alts)
    bounds = get_sheet_bounds(sheet)

    # Ray from near sheet center, pointing outward
    origin = bounds.center_xyz * 1.1  # Just above surface
    direction = bounds.center_xyz / np.linalg.norm(bounds.center_xyz)  # Outward

    could_hit, _approx_point = ray_intersects_bounds(origin, direction, bounds)

    # Should not hit because it's going away
    assert not could_hit


@pytest.mark.parametrize("method", ["sphere", "plane"])
def test_ray_intersects_bounds_methods(method: str) -> None:
    """Test both intersection methods give similar results."""
    lats = np.linspace(39.0, 40.0, 10)
    lons = np.linspace(-105.0, -104.0, 10)
    alts = np.zeros((10, 10))

    sheet = lla_to_xyz_sheet(lats, lons, alts)
    bounds = get_sheet_bounds(sheet)

    origin = bounds.center_xyz * 2
    direction = -bounds.center_xyz

    could_hit, _ = ray_intersects_bounds(origin, direction, bounds, method=method)
    assert could_hit


def test_point_within_bounds_inside() -> None:
    """Test point inside bounds."""
    lats = np.linspace(39.0, 40.0, 10)
    lons = np.linspace(-105.0, -104.0, 10)
    alts = np.zeros((10, 10))

    sheet = lla_to_xyz_sheet(lats, lons, alts)
    bounds = get_sheet_bounds(sheet)

    # Center of sheet should be within bounds
    assert point_within_bounds(bounds.center_xyz, bounds)


def test_point_within_bounds_outside() -> None:
    """Test point outside bounds."""
    lats = np.linspace(39.0, 40.0, 10)
    lons = np.linspace(-105.0, -104.0, 10)
    alts = np.zeros((10, 10))

    sheet = lla_to_xyz_sheet(lats, lons, alts)
    bounds = get_sheet_bounds(sheet)

    # Point far from sheet
    far_point = np.array([0.0, 0.0, 7e6])  # North pole
    assert not point_within_bounds(far_point, bounds)


def test_bounds_overlap_same_sheet() -> None:
    """Test that identical bounds overlap."""
    lats = np.linspace(39.0, 40.0, 10)
    lons = np.linspace(-105.0, -104.0, 10)
    alts = np.zeros((10, 10))

    sheet = lla_to_xyz_sheet(lats, lons, alts)
    bounds = get_sheet_bounds(sheet)

    assert bounds_overlap(bounds, bounds)


def test_bounds_overlap_adjacent() -> None:
    """Test adjacent sheets that should overlap at boundary."""
    # Sheet 1: 39-40 lat
    lats1 = np.linspace(39.0, 40.0, 10)
    lons1 = np.linspace(-105.0, -104.0, 10)
    alts1 = np.zeros((10, 10))
    sheet1 = lla_to_xyz_sheet(lats1, lons1, alts1)
    bounds1 = get_sheet_bounds(sheet1)

    # Sheet 2: 40-41 lat (adjacent)
    lats2 = np.linspace(40.0, 41.0, 10)
    lons2 = np.linspace(-105.0, -104.0, 10)
    alts2 = np.zeros((10, 10))
    sheet2 = lla_to_xyz_sheet(lats2, lons2, alts2)
    bounds2 = get_sheet_bounds(sheet2)

    # Adjacent sheets touch at lat=40
    assert bounds_overlap(bounds1, bounds2)


def test_bounds_overlap_distant() -> None:
    """Test distant sheets that should not overlap."""
    # Sheet 1: 39-40 lat
    lats1 = np.linspace(39.0, 40.0, 10)
    lons1 = np.linspace(-105.0, -104.0, 10)
    alts1 = np.zeros((10, 10))
    sheet1 = lla_to_xyz_sheet(lats1, lons1, alts1)
    bounds1 = get_sheet_bounds(sheet1)

    # Sheet 2: 50-51 lat (far away)
    lats2 = np.linspace(50.0, 51.0, 10)
    lons2 = np.linspace(-105.0, -104.0, 10)
    alts2 = np.zeros((10, 10))
    sheet2 = lla_to_xyz_sheet(lats2, lons2, alts2)
    bounds2 = get_sheet_bounds(sheet2)

    # Distant sheets should not overlap
    assert not bounds_overlap(bounds1, bounds2)
