import math

import numpy as np
import pytest

from rcm.devices import DEVICES
from rcm.propagation import (C, fspl_db, knife_edge_db, path_loss_map, NU_60_FRESNEL)


def test_fspl_reference():
    # 1 km at 2.4 GHz is 100.05 dB (textbook value)
    assert fspl_db(1000, 2.4e9) == pytest.approx(100.05, abs=0.02)


def test_knife_edge_reference_points():
    assert knife_edge_db(0.0) == pytest.approx(6.0, abs=0.1)     # grazing -> ~6 dB
    assert knife_edge_db(-1.0) == 0.0                              # clear
    assert knife_edge_db(2.4) == pytest.approx(20.6, abs=0.3)      # ITU-R P.526 curve


def _flat(n=401, z=10.0):
    t = np.full((n, n), z)
    return t, t.copy()


def test_flat_high_drone_is_free_space():
    t, s = _flat()
    res = 5.0
    r = path_loss_map(s, t, res, alt_m=100, freqs_hz=[2.44e9], k_factor=1e6)
    c = s.shape[0] // 2
    # 200 cells east = 1000 m
    pl = r["pl"][0, c, c + 200]
    d = math.hypot(1000, 100 - 1.2)
    assert pl == pytest.approx(fspl_db(d, 2.44e9), abs=0.05)
    assert r["nu"][0, c, c + 200] < NU_60_FRESNEL


def test_single_wall_matches_analytic_knife_edge():
    """Wall 500 m east of pilot, drone 1000 m east: compare with the textbook formula."""
    t, s = _flat(z=0.0)
    res = 5.0
    c = s.shape[0] // 2
    wall_h = 30.0
    s[:, c + 100] = wall_h  # a N-S wall, one cell thick, at x = 500 m
    alt = 20.0
    f = 2.44e9
    r = path_loss_map(s, t, res, alt_m=alt, freqs_hz=[f], h_pilot=1.2, k_factor=1e6)
    d1, d2 = 500.0, 500.0
    hline = 1.2 + (alt - 1.2) * d1 / (d1 + d2)
    h = wall_h - hline
    lam = C / f
    nu = h * math.sqrt(2 * (d1 + d2) / (lam * d1 * d2))
    got = r["nu"][0, c, c + 200]
    assert got == pytest.approx(nu, rel=0.02)
    expected_pl = fspl_db(math.hypot(1000, alt - 1.2), f) + knife_edge_db(nu)
    assert r["pl"][0, c, c + 200] == pytest.approx(expected_pl, abs=0.3)


def test_terrain_collision_flag_takeoff_mode():
    t, s = _flat(z=0.0)
    c = t.shape[0] // 2
    t[:, c + 150:] = 80.0   # a plateau 80 m above take-off, starting 750 m east
    s[:] = t
    r = path_loss_map(s, t, 5.0, alt_m=50, freqs_hz=[2.44e9], alt_agl=False, k_factor=1e6)
    assert r["collide"][c, c + 180]        # 50 m above take-off is inside the plateau
    assert not r["collide"][c, c + 100]
    r2 = path_loss_map(s, t, 5.0, alt_m=50, freqs_hz=[2.44e9], alt_agl=True, k_factor=1e6)
    assert not r2["collide"][c, c + 180]   # AGL mode follows the terrain


def test_rated_range_budget_consistency():
    d = DEVICES["dji-mini-3-pro"]
    lmax = d.l_max("none")
    # at the rated range, free-space loss equals the budget -> zero margin
    assert np.allclose(lmax, fspl_db(12_000, np.array(d.bands_hz)))
    assert (d.l_max("strong") < d.l_max("medium")).all()
