"""Tests for terrain visibility operations."""

import numpy as np

from gri_utils.conversion import lla_to_xyz
from gri_utils.terrain import (
    get_shadow_boundary,
    get_visibility_mask,
    lla_to_xyz_sheet,
)


def test_visibility_mask_flat_terrain() -> None:
    """All points visible from high altitude over flat terrain."""
    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)

    observer_lla = np.array([39.5, -104.5, 10000.0])
    observer = lla_to_xyz(observer_lla)

    mask = get_visibility_mask(sheet, observer)

    assert mask.shape == (20, 20)
    assert mask.dtype == bool
    assert np.sum(mask) > 0.9 * mask.size


def test_visibility_mask_with_ridge() -> None:
    """Points behind a tall ridge are blocked."""
    lats = np.linspace(39.0, 40.0, 30)
    lons = np.linspace(-105.0, -104.0, 30)
    alts = np.zeros((30, 30))

    # 5km high ridge across middle
    alts[15, :] = 5000.0

    sheet = lla_to_xyz_sheet(lats, lons, alts)

    # Observer on one side of ridge, low altitude
    observer_lla = np.array([39.2, -104.5, 1000.0])
    observer = lla_to_xyz(observer_lla)

    mask = get_visibility_mask(sheet, observer)

    # Points beyond the ridge should be blocked
    blocked_fraction = 1 - np.mean(mask[20:, :])
    assert blocked_fraction > 0.5


def test_shadow_boundary_flat_terrain() -> None:
    """Flat terrain from above has no shadow boundaries."""
    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)

    observer_lla = np.array([39.5, -104.5, 10000.0])
    observer = lla_to_xyz(observer_lla)

    boundaries = get_shadow_boundary(sheet, observer)

    assert isinstance(boundaries, list)


def test_shadow_boundary_with_obstacle() -> None:
    """Terrain obstacle produces shadow boundaries."""
    lats = np.linspace(39.0, 40.0, 50)
    lons = np.linspace(-105.0, -104.0, 50)
    alts = np.zeros((50, 50))

    # Add a hill
    alts[25, 25] = 3000.0
    for di in range(-3, 4):
        for dj in range(-3, 4):
            dist = np.sqrt(di**2 + dj**2)
            r, c = 25 + di, 25 + dj
            if 0 <= r < 50 and 0 <= c < 50:
                alts[r, c] = max(alts[r, c], 3000 - dist * 500)

    sheet = lla_to_xyz_sheet(lats, lons, alts)

    observer_lla = np.array([39.2, -104.5, 1000.0])
    observer = lla_to_xyz(observer_lla)

    boundaries = get_shadow_boundary(sheet, observer)

    assert isinstance(boundaries, list)
