"""Tests for the vessel-heading simulator."""

from __future__ import annotations

import pytest

from arautopilot.studio.simulator.vessel_heading import (
    VesselHeadingConfig,
    VesselHeadingSimulator,
    heading_error_deg,
)


def test_zero_rudder_holds_heading() -> None:
    sim = VesselHeadingSimulator()
    sim.reset(heading_deg=42.0)
    for _ in range(2000):
        sim.step(dt=0.01, rudder_deg=0.0)
    assert sim.state.heading_deg == pytest.approx(42.0, abs=1e-3)


def test_positive_rudder_turns_starboard() -> None:
    sim = VesselHeadingSimulator()
    sim.reset(heading_deg=0.0)
    for _ in range(2000):
        sim.step(dt=0.01, rudder_deg=5.0)
    # After 20 s with +5 deg of rudder, heading should advance (mod 360).
    assert sim.state.rate_of_turn_dps > 0.0
    assert sim.state.heading_deg != 0.0


def test_negative_rudder_turns_port() -> None:
    sim = VesselHeadingSimulator()
    sim.reset(heading_deg=0.0)
    for _ in range(2000):
        sim.step(dt=0.01, rudder_deg=-5.0)
    assert sim.state.rate_of_turn_dps < 0.0


def test_speed_increases_yaw_response() -> None:
    sim_slow = VesselHeadingSimulator(VesselHeadingConfig(speed_kn=5.0))
    sim_fast = VesselHeadingSimulator(VesselHeadingConfig(speed_kn=20.0))
    sim_slow.reset()
    sim_fast.reset()
    for _ in range(2000):
        sim_slow.step(dt=0.01, rudder_deg=5.0)
        sim_fast.step(dt=0.01, rudder_deg=5.0)
    # Fast vessel turns farther in the same time.
    assert abs(sim_fast.state.rate_of_turn_dps) > abs(sim_slow.state.rate_of_turn_dps)


def test_heading_wraps_at_360() -> None:
    sim = VesselHeadingSimulator()
    sim.reset(heading_deg=359.0, rate_of_turn_dps=10.0)
    for _ in range(20):
        sim.step(dt=0.1, rudder_deg=0.0)
    # heading must remain in [0, 360)
    assert 0.0 <= sim.state.heading_deg < 360.0


def test_invalid_dt() -> None:
    sim = VesselHeadingSimulator()
    sim.reset()
    with pytest.raises(ValueError):
        sim.step(dt=0.0, rudder_deg=5.0)


# ----------------------------------------------------------------------------
# heading_error_deg
# ----------------------------------------------------------------------------


@pytest.mark.parametrize("sp, meas, expected", [
    (90.0, 80.0, 10.0),
    (80.0, 90.0, -10.0),
    (0.0, 0.0, 0.0),
    (0.0, 359.0, 1.0),         # crossing 0 going stbd
    (359.0, 0.0, -1.0),
    (180.0, 0.0, 180.0),
    (0.0, 180.0, 180.0),       # ambiguity at 180 -- convention is +180
    (10.0, 350.0, 20.0),
    (350.0, 10.0, -20.0),
])
def test_heading_error_shortest_arc(sp: float, meas: float, expected: float) -> None:
    assert heading_error_deg(sp, meas) == pytest.approx(expected, abs=1e-9)
