"""Tests for ``arautopilot.core.knob_state``."""

from __future__ import annotations

from datetime import UTC

import pytest

from arautopilot.core.knob_state import (
    KnobFunction,
    KnobMode,
    KnobState,
)


def test_idle_state_defaults() -> None:
    s = KnobState.idle()
    assert s.mode is KnobMode.LIBRE
    assert s.function is KnobFunction.NONE
    assert s.pending_value is None
    assert s.armed_at is None
    assert s.timeout_remaining_s == 0.0


def test_libre_state_is_immutable() -> None:
    s = KnobState.idle()
    with pytest.raises((TypeError, ValueError)):
        s.mode = KnobMode.ARMADO  # type: ignore[misc]


def test_arm_transitions_to_armado() -> None:
    s = KnobState.idle().arm(KnobFunction.RUMBO, current_value=180.0)
    assert s.mode is KnobMode.ARMADO
    assert s.function is KnobFunction.RUMBO
    assert s.current_value == pytest.approx(180.0)
    assert s.armed_at is not None
    assert s.timeout_remaining_s > 0


def test_arming_with_none_function_rejected() -> None:
    with pytest.raises(ValueError):
        KnobState.idle().arm(KnobFunction.NONE, current_value=0.0)


def test_arming_from_non_libre_rejected() -> None:
    armed = KnobState.idle().arm(KnobFunction.RUMBO, current_value=90.0)
    with pytest.raises(ValueError):
        armed.arm(KnobFunction.BRILLO, current_value=50.0)


def test_propose_then_confirm_round_trip() -> None:
    armed = KnobState.idle().arm(KnobFunction.RUMBO, current_value=180.0)
    pending = armed.propose(185.0)
    assert pending.mode is KnobMode.CONFIRMANDO
    assert pending.pending_value == pytest.approx(185.0)
    assert pending.current_value == pytest.approx(180.0)

    confirmed = pending.confirm()
    assert confirmed.mode is KnobMode.ARMADO
    assert confirmed.pending_value is None
    assert confirmed.current_value == pytest.approx(185.0)


def test_propose_from_libre_rejected() -> None:
    with pytest.raises(ValueError):
        KnobState.idle().propose(10.0)


def test_confirm_without_pending_rejected() -> None:
    armed = KnobState.idle().arm(KnobFunction.RUMBO, current_value=180.0)
    with pytest.raises(ValueError):
        armed.confirm()


def test_disarm_returns_to_libre() -> None:
    s = (
        KnobState.idle()
        .arm(KnobFunction.RUMBO, current_value=180.0)
        .propose(190.0)
        .disarm()
    )
    assert s == KnobState.idle()


def test_libre_with_armed_at_set_invalid() -> None:
    from datetime import datetime

    with pytest.raises(ValueError):
        KnobState(
            mode=KnobMode.LIBRE,
            function=KnobFunction.NONE,
            armed_at=datetime.now(UTC),
        )


def test_armado_without_armed_at_invalid() -> None:
    with pytest.raises(ValueError):
        KnobState(
            mode=KnobMode.ARMADO,
            function=KnobFunction.RUMBO,
            current_value=180.0,
            armed_at=None,
        )
