"""Tests for marching squares contour finding."""
# ruff: noqa: N806, RUF059  # Allow uppercase for meshgrid X, Y; allow unused Y

import numpy as np
from numpy.testing import assert_allclose

from gri_utils.terrain import contours_to_xyz, find_contours, lla_to_xyz_sheet


def test_find_contours_circle() -> None:
    """Test finding contours of a circle."""
    # Create a scalar field: x^2 + y^2 - 0.5
    # Zero contour is a circle of radius sqrt(0.5)
    n = 50
    x = np.linspace(-1, 1, n)
    y = np.linspace(-1, 1, n)
    X, Y = np.meshgrid(x, y)
    values = X**2 + Y**2 - 0.5

    contours = find_contours(values, level=0.0)

    assert len(contours) == 1
    contour = contours[0]
    assert contour.shape[1] == 2  # (K, 2) array

    # Contour points should be roughly on circle of radius sqrt(0.5)
    # Convert grid coords to physical coords
    contour_x = (contour[:, 1] / (n - 1)) * 2 - 1
    contour_y = (contour[:, 0] / (n - 1)) * 2 - 1
    radii = np.sqrt(contour_x**2 + contour_y**2)
    expected_radius = np.sqrt(0.5)
    assert_allclose(radii, expected_radius, rtol=0.1)


def test_find_contours_line() -> None:
    """Test finding contours of a linear field."""
    # Linear field: x - 0.5 = 0 is a vertical line
    n = 20
    x = np.linspace(0, 1, n)
    y = np.linspace(0, 1, n)
    X, Y = np.meshgrid(x, y)
    values = X - 0.5

    contours = find_contours(values, level=0.0)

    assert len(contours) == 1
    contour = contours[0]

    # All points should have col coordinate near center (0.5 * (n-1))
    expected_col = 0.5 * (n - 1)
    assert_allclose(contour[:, 1], expected_col, atol=0.6)  # Within 1 grid cell


def test_find_contours_no_crossing() -> None:
    """Test field with no zero crossing."""
    values = np.ones((10, 10))  # All positive

    contours = find_contours(values, level=0.0)

    assert len(contours) == 0


def test_find_contours_all_below() -> None:
    """Test field entirely below level."""
    values = -np.ones((10, 10))

    contours = find_contours(values, level=0.0)

    assert len(contours) == 0


def test_find_contours_saddle_point() -> None:
    """Test field with saddle point."""
    # Saddle: x*y has zero contours along x=0 and y=0
    n = 20
    x = np.linspace(-1, 1, n)
    y = np.linspace(-1, 1, n)
    X, Y = np.meshgrid(x, y)
    values = X * Y

    contours = find_contours(values, level=0.0)

    # Should find two crossing contours (x=0 and y=0 lines)
    assert len(contours) >= 2


def test_find_contours_multiple_levels() -> None:
    """Test finding contours at different levels."""
    n = 30
    x = np.linspace(-2, 2, n)
    y = np.linspace(-2, 2, n)
    X, Y = np.meshgrid(x, y)
    values = X**2 + Y**2

    # Find contours at radii 0.5, 1.0, 1.5
    for level in [0.25, 1.0, 2.25]:
        contours = find_contours(values, level=level)
        assert len(contours) >= 1


def test_find_contours_small_grid() -> None:
    """Test with minimal grid size."""
    values = np.array([[1, -1], [-1, 1]])

    contours = find_contours(values, level=0.0)

    # Should find contours even on 2x2 grid
    assert len(contours) >= 1


def test_find_contours_single_row() -> None:
    """Test with grid that's too small."""
    values = np.array([[1, 2, 3]])  # 1 row only

    contours = find_contours(values, level=0.0)

    assert len(contours) == 0  # Can't find contours in 1D


def test_contours_to_xyz() -> None:
    """Test conversion of contours to XYZ coordinates."""
    # Create terrain sheet
    lats = np.linspace(39.0, 40.0, 20)
    lons = np.linspace(-105.0, -104.0, 20)
    alts = np.zeros((20, 20))
    sheet = lla_to_xyz_sheet(lats, lons, alts)

    # Create simple contour in grid coordinates
    contours = [np.array([[5.0, 5.0], [5.0, 10.0], [10.0, 10.0]])]

    xyz_contours = contours_to_xyz(contours, sheet)

    assert len(xyz_contours) == 1
    assert xyz_contours[0].shape == (3, 3)  # 3 points, 3 coords each


def test_contours_to_xyz_empty() -> None:
    """Test conversion with empty contour list."""
    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)

    xyz_contours = contours_to_xyz([], sheet)

    assert len(xyz_contours) == 0


def test_find_contours_returns_arrays() -> None:
    """Test that find_contours returns numpy arrays."""
    n = 20
    x = np.linspace(-1, 1, n)
    y = np.linspace(-1, 1, n)
    X, Y = np.meshgrid(x, y)
    values = X**2 + Y**2 - 0.5

    contours = find_contours(values, level=0.0)

    for contour in contours:
        assert isinstance(contour, np.ndarray)
        assert contour.dtype == np.float64
