"""
Unit tests for src/core/geo.py (INDL-43 — Haversine + walking-time util).

Covers boundary values (poles, antimeridian, identical coords, zero distance) and
out-of-range inputs returning None rather than raising (SEC-07 / CIS 16).
"""
import math

import pytest

from src.core.geo import (
    WALKING_SPEED_M_PER_S,
    estimate_walking_minutes,
    haversine_distance_m,
)


def test_zero_distance_identical_coords():
    assert haversine_distance_m(45.0, -75.0, 45.0, -75.0) == pytest.approx(0.0, abs=1e-6)


def test_known_short_distance():
    # ~111 m apart (0.001 deg latitude ≈ 111 m).
    d = haversine_distance_m(45.0000, -75.0, 45.0010, -75.0)
    assert d == pytest.approx(111.0, abs=2.0)


def test_symmetry():
    a = haversine_distance_m(51.5, -0.12, 48.85, 2.35)
    b = haversine_distance_m(48.85, 2.35, 51.5, -0.12)
    assert a == pytest.approx(b, rel=1e-9)


@pytest.mark.parametrize(
    "lat1,lng1,lat2,lng2",
    [
        (90, 0, -90, 0),        # pole to pole
        (0, 179.999, 0, -179.999),  # antimeridian crossing
        (-90, -180, 90, 180),   # extreme corners
    ],
)
def test_boundary_values_do_not_raise(lat1, lng1, lat2, lng2):
    d = haversine_distance_m(lat1, lng1, lat2, lng2)
    assert d is not None
    assert d >= 0
    assert math.isfinite(d)


@pytest.mark.parametrize(
    "lat1,lng1,lat2,lng2",
    [
        (None, 0, 0, 0),
        (0, None, 0, 0),
        (91, 0, 0, 0),          # lat out of range
        (0, 181, 0, 0),         # lng out of range
        (-91, 0, 0, 0),
        ("abc", 0, 0, 0),       # non-numeric
        (float("nan"), 0, 0, 0),
        (float("inf"), 0, 0, 0),
    ],
)
def test_invalid_inputs_return_none(lat1, lng1, lat2, lng2):
    assert haversine_distance_m(lat1, lng1, lat2, lng2) is None


def test_walking_minutes_rounds_up_min_one():
    # 1 metre → still 1 minute (floor).
    assert estimate_walking_minutes(1) == 1
    # exactly zero → 0.
    assert estimate_walking_minutes(0) == 0
    # 180 m at 1.2 m/s = 150 s = 2.5 min → ceil 3.
    assert estimate_walking_minutes(180) == 3


def test_walking_minutes_matches_speed_constant():
    # 1 minute of walking distance → 1 minute.
    one_minute_m = WALKING_SPEED_M_PER_S * 60
    assert estimate_walking_minutes(one_minute_m) == 1
    assert estimate_walking_minutes(one_minute_m + 1) == 2


@pytest.mark.parametrize("bad", [None, -5, float("nan"), float("inf"), "x"])
def test_walking_minutes_invalid_returns_none(bad):
    assert estimate_walking_minutes(bad) is None
