import math

import pytest

from data import grid


def test_snap_is_deterministic_and_contains_point():
    a = grid.snap(47.6062, -122.3321)
    b = grid.snap(47.6062, -122.3321)
    assert a == b
    assert a["bounds"]["south"] <= 47.6062 <= a["bounds"]["north"]


def test_snap_center_lands_in_same_cell():
    cell = grid.snap(40.7128, -74.0060)
    again = grid.snap(cell["center_lat"], cell["center_lon"])
    assert again["id"] == cell["id"]


def test_from_id_round_trip():
    cell = grid.snap(35.6762, 139.6503)   # Tokyo — worldwide coverage
    rebuilt = grid.from_id(cell["id"])
    assert rebuilt == cell


def test_from_id_rejects_malformed():
    with pytest.raises(ValueError):
        grid.from_id("not-a-cell")


def test_longitude_wraps_across_antimeridian():
    assert grid.snap(10.0, 200.0)["id"] == grid.snap(10.0, -160.0)["id"]
    assert grid.snap(10.0, 540.0)["id"] == grid.snap(10.0, 180.0)["id"]


def test_latitude_clamped_at_poles():
    cell = grid.snap(95.0, 0.0)
    assert cell == grid.snap(90.0, 0.0)
    assert -90.0 <= cell["center_lat"] <= 90.0
    assert -180.0 <= cell["center_lon"] <= 180.0


def test_reported_center_is_a_valid_coordinate_everywhere():
    for lat, lon in [(89.99, 179.99), (-89.99, -179.99), (0.0, 0.0), (66.5, -179.5)]:
        cell = grid.snap(lat, lon)
        assert -90.0 <= cell["center_lat"] <= 90.0
        assert -180.0 <= cell["center_lon"] <= 180.0


def test_cells_stay_roughly_square():
    # The cos(lat) scaling should keep the area near ~4 sq mi away from the poles.
    for lat in (0.0, 30.0, 45.0, 60.0):
        cell = grid.snap(lat, 10.0)
        assert cell["area_sq_mi"] == pytest.approx(4.0, rel=0.15)


def test_lon_step_clamps_near_poles():
    # cos(89°) ~ 0.017 would blow the step up; the 0.05 clamp caps it.
    assert grid._lon_step(89.0) == pytest.approx(grid.LAT_STEP / 0.05)
    assert not math.isinf(grid._lon_step(90.0))


def test_neighbors_mid_latitude():
    cell = grid.snap(47.6062, -122.3321)
    ns = grid.neighbors(cell)
    assert len(ns) == 8
    ids = {n["id"] for n in ns}
    assert cell["id"] not in ids and len(ids) == 8
    # Every neighbor is at most one row away and shares a border region.
    i = int(cell["id"].split("_")[0])
    assert all(abs(int(n["id"].split("_")[0]) - i) <= 1 for n in ns)


def test_neighbors_skip_rows_past_the_pole():
    cell = grid.snap(89.99, 10.0)     # topmost row
    ns = grid.neighbors(cell)
    assert 0 < len(ns) < 8            # no row above the pole
    assert all(-90 <= n["center_lat"] <= 90 for n in ns)


def test_neighbors_wrap_across_antimeridian():
    cell = grid.snap(10.0, 179.999)
    ns = grid.neighbors(cell)
    assert len(ns) == 8
    assert any(n["center_lon"] < 0 for n in ns)   # some sit past the date line
