"""Tests for DIO (Digital Output) functionality."""

from __future__ import annotations

from unittest.mock import patch

import pytest

from radiens_core._generated import allegoserver_pb2
from radiens_core.allego_client import AllegoClient
from radiens_core.models.dio import DigitalOutChannelRegister, DigitalOutRegister, DIOMode


def test_dio_models() -> None:
    """Test DIO domain models initialization."""
    chan = DigitalOutChannelRegister(
        manual_state=True,
        event_threshold=100.0,
        event_polarity=True,
        ntv_chan_idx=0,
        pulse_interval_sec=1.0,
        pulse_duration_sec=0.1,
    )
    assert chan.manual_state is True
    assert chan.event_threshold == 100.0
    assert chan.event_polarity is True

    reg = DigitalOutRegister(
        mode=DIOMode.PULSE,
        dout_chan_registers=[chan],
        gated_polarity=True,
        req_pulse_interval_sec=2.0,
        req_pulse_duration_sec=0.2,
        req_pulse_ntv_chan_idx=-1,
    )
    assert reg.mode == DIOMode.PULSE
    assert len(reg.dout_chan_registers) == 1
    assert reg.req_pulse_interval_sec == 2.0


def test_dio_converters() -> None:
    """Test DIO proto <-> domain converters."""
    from radiens_core._converters._dio import (
        digital_out_channel_register_from_proto,
        digital_out_register_from_proto,
        dio_mode_from_proto,
        dio_mode_to_proto,
    )

    # DIOMode
    assert dio_mode_to_proto(DIOMode.MANUAL) == allegoserver_pb2.manual
    assert dio_mode_from_proto(allegoserver_pb2.pulse) == DIOMode.PULSE

    # Register
    proto_chan = allegoserver_pb2.DigitalOutChannelRegister(
        manualState=False,
        eventThreshold=50.0,
        eventPolarity=False,
        ntvChanIdx=1,
        pulseIntervalSec=0.5,
        pulseDurationSec=0.05,
    )
    domain_chan = digital_out_channel_register_from_proto(proto_chan)
    assert domain_chan.manual_state is False
    assert domain_chan.event_threshold == 50.0
    assert domain_chan.ntv_chan_idx == 1

    proto_reg = allegoserver_pb2.DigitalOutRegister(
        mode=allegoserver_pb2.events,
        gatedPolarity=False,
        reqPulseIntervalSec=1.0,
        reqPulseDurationSec=0.1,
        reqPulseNtvChanIdx=0,
    )
    proto_reg.doutChanRegisters.append(proto_chan)

    domain_reg = digital_out_register_from_proto(proto_reg)
    assert domain_reg.mode == DIOMode.EVENTS
    assert len(domain_reg.dout_chan_registers) == 1
    assert domain_reg.req_pulse_duration_sec == pytest.approx(0.1)
    assert domain_reg.dout_chan_registers[0].event_threshold == pytest.approx(50.0)


def test_allego_client_dio_methods() -> None:
    """Test AllegoClient DIO methods call correct service functions."""
    with (
        patch("radiens_core._base_client.AuthInterceptor"),
        patch("radiens_core.allego_client.get_dio_reg_proto") as mock_get_reg,
        patch("radiens_core.allego_client.set_dio_events_proto") as mock_set_events,
        patch("radiens_core.allego_client.set_dio_manual_proto") as mock_set_manual,
        patch("radiens_core.allego_client.set_dio_gated_proto") as mock_set_gated,
        patch("radiens_core.allego_client.set_dio_pulse_proto") as mock_set_pulse,
        patch("radiens_core.allego_client.validate_proto"),
    ):
        # Setup mock return for get_dio_reg
        proto_reg = allegoserver_pb2.DigitalOutRegister(mode=allegoserver_pb2.manual)
        mock_get_reg.return_value = proto_reg

        client = AllegoClient()

        # get_dio_reg
        reg = client.get_dio_reg()
        assert reg.mode == DIOMode.MANUAL
        mock_get_reg.assert_called_once()

        # set_dio_events
        client.set_dio_events(chan_idx=0, threshold=100.0, polarity=True)
        mock_set_events.assert_called_once()
        args, _ = mock_set_events.call_args
        assert args[2].chanIdx == 0
        assert args[2].eventThreshold == pytest.approx(100.0)
        assert args[2].eventPolarity is True

        # set_dio_manual
        client.set_dio_manual(chan_idx=1, state=True)
        mock_set_manual.assert_called_once()
        args, _ = mock_set_manual.call_args
        assert args[2].chanIdx == 1
        assert args[2].state is True

        # set_dio_gated
        client.set_dio_gated(polarity=False)
        mock_set_gated.assert_called_once()
        args, _ = mock_set_gated.call_args
        assert args[2].gatedPolarity is False

        # set_dio_pulse
        client.set_dio_pulse(interval_sec=1.5, duration_sec=0.15, chan_idx=2)
        mock_set_pulse.assert_called_once()
        args, _ = mock_set_pulse.call_args
        assert args[2].reqIntervalSec == pytest.approx(1.5)
        assert args[2].reqDurationSec == pytest.approx(0.15)
        assert args[2].reqNtvChanIdx == 2
