"""Tests for the GUI-agnostic planning-session helpers."""
import numpy as np
from sulcora.volume import Volume
from sulcora.planning import Trajectory
from sulcora.optimizer import RiskStructure
from sulcora import session as S


def _struct(name, voxels, shape=(31, 31, 31), margin=1.0):
    d = np.zeros(shape, np.uint8)
    for v in voxels:
        d[v] = 1
    return RiskStructure(name, Volume(d, np.eye(4)), margin)


def test_default_entry_radius():
    vol = Volume(np.zeros((40, 40, 40), np.float32), np.eye(4))
    r = S.default_entry_radius(vol, (20, 20, 20), axis=(0, 0, 1), minimum=5.0)
    # far z surface ~39; distance from target z=20 is ~19; 0.9x -> ~17
    assert 15.0 < r < 18.0
    # clamps to the minimum for a target already near the surface
    assert S.default_entry_radius(vol, (20, 20, 39), minimum=20.0) == 20.0


def test_risk_summary_nearest_and_bands():
    s1 = _struct("eloquent", [(5, 15, 15)])     # ~4 mm from the path below
    s2 = _struct("vessel", [(7, 15, 15)])       # ~2 mm
    traj = Trajectory(entry=(9, 0, 15), target=(9, 30, 15))
    rep = S.risk_summary(traj, [s1, s2], n=201)
    assert rep["nearest"] == "vessel"
    assert abs(rep["min_clearance_mm"] - 2.0) < 0.2
    assert rep["band"] == "HIGH"
    assert abs(rep["per_structure"]["eloquent"]["clearance_mm"] - 4.0) < 0.2


def test_risk_summary_deformation_aware():
    from sulcora.deformation import ProbeDeformation
    s = _struct("vessel", [(5, 15, 15)])
    traj = Trajectory(entry=(9, 0, 15), target=(9, 30, 15))
    pd = ProbeDeformation(traj, probe_radius_mm=1.5, influence_radius_mm=18.0)
    rep = S.risk_summary(traj, [s], deformation=pd, n=201)
    ps = rep["per_structure"]["vessel"]
    assert "deformed_clearance_mm" in ps
    assert ps["deformed_clearance_mm"] != ps["clearance_mm"]
    assert "deformed_min_clearance_mm" in rep


def test_risk_summary_empty_structures():
    rep = S.risk_summary(Trajectory((0, 0, 0), (0, 0, 10)), [])
    assert rep["nearest"] is None and rep["band"] == "LOW"
    assert rep["min_clearance_mm"] == float("inf")


def test_clearance_field_encodes_per_voxel_distance():
    # two structure voxels at different distances from a vertical line at x=10
    d = np.zeros((21, 21, 21), np.uint8)
    d[6, 10, 10] = 1        # 4 mm from the line x=10 (in x)
    d[3, 10, 10] = 1        # 7 mm from the line
    mask = Volume(d, np.eye(4))
    traj = Trajectory(entry=(10, 0, 10), target=(10, 20, 10))
    field = S.clearance_field(traj, mask, n=201)
    assert abs(field.data[6, 10, 10] - 4.0) < 0.2
    assert abs(field.data[3, 10, 10] - 7.0) < 0.2
    assert field.data[0, 0, 0] == 0.0          # background stays zero
    # closer voxel has the smaller clearance (redder end of grey->red map)
    assert field.data[6, 10, 10] < field.data[3, 10, 10]


def test_clearance_field_empty_mask():
    mask = Volume(np.zeros((8, 8, 8), np.uint8), np.eye(4))
    f = S.clearance_field(Trajectory((0, 0, 0), (0, 0, 7)), mask)
    assert float(f.data.sum()) == 0.0


def test_clearance_profile_min_and_location():
    s = _struct("vessel", [(6, 15, 15)])       # 4 mm from the line x=10 at its nearest
    traj = Trajectory(entry=(10, 0, 15), target=(10, 30, 15))
    depths, best, per = S.clearance_profile(traj, [s], n=61)
    assert len(depths) == 61 and depths[0] == 0.0
    assert abs(depths[-1] - traj.length) < 1e-6
    assert abs(best.min() - 4.0) < 0.3          # closest approach ~4 mm
    assert "vessel" in per


def test_lesion_info_and_plan_to_lesion():
    vol = Volume(np.zeros((31, 31, 31), np.float32), np.eye(4))
    # a small "haematoma" cube near the centre
    d = np.zeros((31, 31, 31), np.uint8); d[14:17, 14:17, 8:11] = 1
    lesion = Volume(d, np.eye(4))
    info = S.lesion_info(lesion)
    assert info["voxels"] == 27
    assert np.allclose(info["centroid"], [15, 15, 9], atol=1e-6)
    assert info["volume_cc"] == round(27 / 1000.0, 1)
    vessel = _struct("vessel", [(5, 15, 20)])
    plans, info2 = S.plan_to_lesion(vol, lesion, [vessel], max_angle_deg=45, n_theta=4, n_phi=8)
    assert info2["voxels"] == 27 and len(plans) > 1
    assert plans[0].score >= plans[-1].score


def test_recommend_returns_ranked_plans():
    vol = Volume(np.zeros((31, 31, 31), np.float32), np.eye(4))
    s = _struct("vessel", [(5, 15, 8)])
    plans = S.recommend(vol, (15, 15, 6), [s], max_angle_deg=45, n_theta=4, n_phi=8)
    assert len(plans) > 1
    assert plans[0].score >= plans[-1].score            # sorted best-first
