"""Tests for validation metrics, interop export, and tract avoidance."""
import os
import numpy as np
import pytest
from sulcora.volume import Volume
from sulcora.planning import Trajectory
from sulcora import validate as V, interop as IO, tracts as T


def _ball(center, r, shape=(40, 40, 40), spacing=1.0):
    ii, jj, kk = np.meshgrid(*[np.arange(s) for s in shape], indexing="ij")
    d = ((ii - center[0]) ** 2 + (jj - center[1]) ** 2 + (kk - center[2]) ** 2 <= r ** 2)
    aff = np.diag([spacing, spacing, spacing, 1.0])
    return Volume(d.astype(np.uint8), aff)


# --------------------------- validation metrics --------------------------- #
def test_dice_and_metrics_identical_and_disjoint():
    a = _ball((20, 20, 20), 8)
    assert V.dice(a, a) == 1.0 and V.jaccard(a, a) == 1.0
    b = _ball((5, 5, 5), 3)
    assert V.dice(a, b) == 0.0
    m = V.segmentation_metrics(a, a)
    assert m["dice"] == 1.0 and m["hausdorff95_mm"] == 0.0 and m["assd_mm"] == 0.0
    assert m["volume_error_cc"] == 0.0


def test_metrics_partial_overlap_reasonable():
    a = _ball((20, 20, 20), 8)
    b = _ball((22, 20, 20), 8)                       # shifted 2 mm
    m = V.segmentation_metrics(a, b)
    assert 0.0 < m["dice"] < 1.0
    assert m["hausdorff95_mm"] > 0 and np.isfinite(m["hausdorff95_mm"])
    assert m["assd_mm"] > 0


def test_trajectory_error():
    p = Trajectory(np.array([0, 0, 30.0]), np.array([0, 0, 0.0]))
    r = Trajectory(np.array([3, 0, 30.0]), np.array([0, 0, 0.0]))
    e = V.trajectory_error(p, r)
    assert e["entry_error_mm"] == 3.0 and e["target_error_mm"] == 0.0
    assert e["angle_error_deg"] > 0


def test_summarize():
    ms = [{"dice": 0.9, "hausdorff95_mm": 2.0}, {"dice": 0.8, "hausdorff95_mm": 4.0}]
    s = V.summarize(ms)
    assert s["n"] == 2 and abs(s["dice"]["mean"] - 0.85) < 1e-6


# ------------------------------- interop ---------------------------------- #
def test_export_json_and_fcsv(tmp_path):
    e, t = [10, 20, 30], [10, 20, 0]
    jp = IO.export_json(str(tmp_path / "p.json"), e, t, meta={"procedure": "dbs"})
    import json
    rec = json.load(open(jp))
    assert rec["entry"] == [10, 20, 30] and rec["length_mm"] == 30.0
    fp = IO.export_fcsv(str(tmp_path / "p.fcsv"), e, t)
    txt = open(fp).read()
    assert "CoordinateSystem = RAS" in txt and "Entry" in txt and "Target" in txt


def test_export_rtstruct_readable(tmp_path):
    pydicom = pytest.importorskip("pydicom")
    e, t = [10, 20, 30], [10, 20, 0]
    rp = IO.export_rtstruct(str(tmp_path / "plan.dcm"), e, t)
    ds = pydicom.dcmread(rp)
    assert ds.Modality == "RTSTRUCT"
    names = [r.ROIName for r in ds.StructureSetROISequence]
    assert names == ["Entry", "Target", "Trajectory"]
    # Entry point stored LPS: RAS (10,20,30) -> (-10,-20,30)
    entry_contour = ds.ROIContourSequence[0].ContourSequence[0].ContourData
    assert [float(x) for x in entry_contour] == [-10.0, -20.0, 30.0]


# -------------------------------- tracts ---------------------------------- #
def test_streamlines_to_mask_and_clearance():
    # a straight streamline along x at y=z=20
    sl = [np.array([[x, 20.0, 20.0] for x in range(5, 35)])]
    mask = T.streamlines_to_mask(sl, np.eye(4), (40, 40, 40), radius_vox=1)
    assert int(mask.data.sum()) > 0 and mask.data[20, 20, 20] == 1
    rs = T.tract_risk_structure(mask, "CST", min_clearance_mm=3.0)
    assert rs.name == "CST" and rs.min_clearance_mm == 3.0
    # a trajectory passing far from the tract has large clearance
    far = Trajectory(np.array([20, 20, 39.0]), np.array([20, 20, 30.0]))
    assert T.tract_clearance(far, mask) >= 5.0
    # one crossing the tract has ~0 clearance
    near = Trajectory(np.array([20, 20, 39.0]), np.array([20, 20, 20.0]))
    assert T.tract_clearance(near, mask) < 2.0
