"""Tests for Time Difference of Arrival (TDOA) observable computation.

Tests verify that get_tdoa correctly computes the time difference of signal
arrival at two collectors.
"""

import numpy as np

from gri_utils.constants import C
from gri_utils.observables import get_tdoa

# Basic computation


def test_tdoa_basic() -> None:
    """Verify TDOA computation for simple geometry."""
    # Emitter directly between two collectors
    emit_xyz = np.array([6378137.0, 0.0, 0.0])
    c1_xyz = np.array([6378137.0, -1000.0, 0.0])
    c2_xyz = np.array([6378137.0, 1000.0, 0.0])

    tdoa = get_tdoa(emit_xyz, c1_xyz, c2_xyz)

    # Emitter equidistant from both collectors
    assert np.isclose(tdoa, 0.0, atol=1e-15)


def test_tdoa_equals_range_diff_over_c() -> None:
    """Verify TDOA equals (r1 - r2) / c."""
    emit_xyz = np.array([6378137.0, 500.0, 300.0])
    c1_xyz = np.array([6378137.0, -1000.0, 0.0])
    c2_xyz = np.array([6378137.0, 1000.0, 0.0])

    tdoa = get_tdoa(emit_xyz, c1_xyz, c2_xyz)

    r1 = np.linalg.norm(emit_xyz - c1_xyz)
    r2 = np.linalg.norm(emit_xyz - c2_xyz)
    expected = (r1 - r2) / C

    assert np.isclose(tdoa, expected, atol=1e-15)


def test_tdoa_positive_when_closer_to_c2() -> None:
    """Verify TDOA is positive when emitter is closer to c2."""
    emit_xyz = np.array([6378137.0, 500.0, 0.0])  # Closer to c2
    c1_xyz = np.array([6378137.0, -1000.0, 0.0])
    c2_xyz = np.array([6378137.0, 1000.0, 0.0])

    tdoa = get_tdoa(emit_xyz, c1_xyz, c2_xyz)

    # Positive TDOA means signal arrives at c2 first (r1 > r2)
    assert tdoa > 0


def test_tdoa_negative_when_closer_to_c1() -> None:
    """Verify TDOA is negative when emitter is closer to c1."""
    emit_xyz = np.array([6378137.0, -500.0, 0.0])  # Closer to c1
    c1_xyz = np.array([6378137.0, -1000.0, 0.0])
    c2_xyz = np.array([6378137.0, 1000.0, 0.0])

    tdoa = get_tdoa(emit_xyz, c1_xyz, c2_xyz)

    # Negative TDOA means signal arrives at c1 first (r1 < r2)
    assert tdoa < 0


def test_tdoa_magnitude_realistic() -> None:
    """Verify TDOA has realistic magnitude for 1km range difference."""
    emit_xyz = np.array([6378137.0, 500.0, 0.0])
    c1_xyz = np.array([6378137.0, -1000.0, 0.0])
    c2_xyz = np.array([6378137.0, 1000.0, 0.0])

    tdoa = get_tdoa(emit_xyz, c1_xyz, c2_xyz)

    # 1000m range diff / 299792458 m/s ≈ 3.336 microseconds
    expected_tdoa = 1000.0 / C
    assert np.isclose(tdoa, expected_tdoa, atol=1e-12)


# Batch processing


def test_tdoa_batch_emitters() -> None:
    """Verify batch processing with multiple emitters."""
    emits_xyz = np.array(
        [
            [6378137.0, 0.0, 0.0],
            [6378137.0, 500.0, 0.0],
            [6378137.0, -500.0, 0.0],
        ],
    )
    c1_xyz = np.array([6378137.0, -1000.0, 0.0])
    c2_xyz = np.array([6378137.0, 1000.0, 0.0])

    tdoas = get_tdoa(emits_xyz, c1_xyz, c2_xyz)

    assert tdoas.shape == (3,)
    assert np.isclose(tdoas[0], 0.0, atol=1e-15)  # Equidistant
    assert tdoas[1] > 0  # Closer to c2
    assert tdoas[2] < 0  # Closer to c1


def test_tdoa_single_returns_scalar() -> None:
    """Verify single emitter returns scalar-like value."""
    emit_xyz = np.array([6378137.0, 0.0, 0.0])
    c1_xyz = np.array([6378137.0, -1000.0, 0.0])
    c2_xyz = np.array([6378137.0, 1000.0, 0.0])

    tdoa = get_tdoa(emit_xyz, c1_xyz, c2_xyz)

    # Should be 0-dimensional or scalar
    assert tdoa.ndim == 0 or tdoa.shape == ()


# Input format flexibility


def test_accepts_lists() -> None:
    """Verify function accepts Python lists."""
    emit_xyz = [6378137.0, 0.0, 0.0]
    c1_xyz = [6378137.0, -1000.0, 0.0]
    c2_xyz = [6378137.0, 1000.0, 0.0]

    tdoa = get_tdoa(emit_xyz, c1_xyz, c2_xyz)

    assert np.isclose(tdoa, 0.0, atol=1e-15)
