#!/usr/bin/env python3
"""
Unit tests for FlowTorch bias mode steering.

Tests the φ₀→letter mapping with bias vectors, including:
- Quadrant wins on axes (0°, 90°, 180°, 270°)
- Diagonal tie-breaking (45°, 135°, 225°, 315°)
- NaN resilience
- Normalization of φ₀ values
"""
import math
import sys
from pathlib import Path

# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))

from edge_app.serve_flowtorch import phi0_bias_vector, _norm_phi0


def test_quadrant_wins_on_axes():
    """Verify each axis (0°, 90°, 180°, 270°) strongly biases its quadrant."""
    # 0° should maximize A
    v0 = phi0_bias_vector(0.0, 2.0)
    assert v0[0] > v0[1] and v0[0] > v0[2] and v0[0] > v0[3], f"0° should prefer A, got {v0}"

    # 90° should maximize B
    v90 = phi0_bias_vector(90.0, 2.0)
    assert v90[1] > v90[0] and v90[1] > v90[2] and v90[1] > v90[3], f"90° should prefer B, got {v90}"

    # 180° should maximize C
    v180 = phi0_bias_vector(180.0, 2.0)
    assert v180[2] > v180[0] and v180[2] > v180[1] and v180[2] > v180[3], f"180° should prefer C, got {v180}"

    # 270° should maximize D
    v270 = phi0_bias_vector(270.0, 2.0)
    assert v270[3] > v270[0] and v270[3] > v270[1] and v270[3] > v270[2], f"270° should prefer D, got {v270}"


def test_diagonal_tiebreak_deterministic():
    """
    Diagonals (45°, 135°, etc.) should produce equal values for adjacent quadrants.
    With epsilon tie-breaking, this is deterministic, but here we just verify
    the bias vector itself is symmetric and contains no NaNs.
    """
    a = phi0_bias_vector(45.0, 2.0)
    # At 45°, A and B should be equal (cos(45°) = cos(45°))
    assert abs(a[0] - a[1]) < 1e-6, f"45° should make A≈B, got {a}"
    # C and D should be equal and negative
    assert abs(a[2] - a[3]) < 1e-6, f"45° should make C≈D, got {a}"
    # No NaNs
    assert not any(math.isnan(x) for x in a), f"NaN detected in bias vector: {a}"


def test_phi0_normalization():
    """Verify _norm_phi0 correctly normalizes angles to [0, 360)."""
    assert _norm_phi0(0) == 0.0
    assert _norm_phi0(360) == 0.0
    assert _norm_phi0(450) == 90.0
    assert _norm_phi0(-90) == 270.0
    assert _norm_phi0(720) == 0.0
    # Invalid inputs should return 0.0
    assert _norm_phi0("invalid") == 0.0
    assert _norm_phi0(None) == 0.0


def test_bias_vector_no_nans():
    """Verify bias vector never produces NaNs for various inputs."""
    test_angles = [0, 30, 45, 60, 90, 120, 135, 150, 180, 210, 225, 240, 270, 300, 315, 330]
    for angle in test_angles:
        vec = phi0_bias_vector(angle, 2.0)
        assert len(vec) == 4, f"Bias vector should have 4 elements, got {len(vec)}"
        assert not any(math.isnan(x) for x in vec), f"NaN in bias vector at φ₀={angle}: {vec}"
        assert not any(math.isinf(x) for x in vec), f"Inf in bias vector at φ₀={angle}: {vec}"


def test_bias_strength_scaling():
    """Verify bias strength parameter scales the vector magnitude."""
    vec1 = phi0_bias_vector(45.0, 1.0)
    vec2 = phi0_bias_vector(45.0, 2.0)

    # vec2 should be approximately 2× vec1
    for v1, v2 in zip(vec1, vec2):
        assert abs(v2 - 2 * v1) < 1e-6, f"Strength=2 should be 2× strength=1: {v1} vs {v2}"


if __name__ == "__main__":
    # Run all tests
    tests = [
        test_quadrant_wins_on_axes,
        test_diagonal_tiebreak_deterministic,
        test_phi0_normalization,
        test_bias_vector_no_nans,
        test_bias_strength_scaling,
    ]

    passed = 0
    failed = 0

    for test in tests:
        try:
            test()
            print(f"✅ {test.__name__}")
            passed += 1
        except AssertionError as e:
            print(f"❌ {test.__name__}: {e}")
            failed += 1
        except Exception as e:
            print(f"💥 {test.__name__}: {e}")
            failed += 1

    print(f"\n{passed} passed, {failed} failed")
    sys.exit(0 if failed == 0 else 1)
