"""Laser-control protocol compatibility tests."""

from __future__ import annotations

import unittest
from unittest.mock import patch

from python_app.hardware_full.laser_control.controller import DEVICE_MAIN_MESSAGE_ID, LaserController
from python_app.hardware_full.laser_control.models import VariationType
from python_app.hardware_full.laser_control.protocol import Protocol, TaskType
from python_app.hardware_full.kamil_adc_service import apply_kamil_adc_laser_control
from python_app.models.run_config_model import RunConfigModel


DEVICE_MAIN_MANUAL_HEX = "1111ff37ffa518ab000000000000000a8000000a8000ff003d2acc2c163f"
DEVICE_MAIN_CHANGE_CURRENT_LD1_HEX = (
    "7777ff3701003d2acc2c10008813ffa5cc2c18ab0a00000a8000000a8000b600"
)
DEVICE_MAIN_CHANGE_CURRENT_LD2_HEX = (
    "7777ff3702003d2acc2c0500881318ab3d2affa50a00000a8000000a80005106"
)


class _FakeProtocol:
    def __init__(self) -> None:
        self.is_connected = True
        self.sent: list[bytes] = []

    def send_raw(self, data: bytes) -> None:
        self.sent.append(bytes(data))

    def receive_raw(self, length: int) -> bytes:
        return b"\x00\x00" if length == 2 else b""


class _FakeLaserController:
    instances: list["_FakeLaserController"] = []

    def __init__(self, **kwargs: object) -> None:
        self.kwargs = kwargs
        self.calls: list[tuple[str, object]] = []
        _FakeLaserController.instances.append(self)

    def connect(self) -> bool:
        self.calls.append(("connect", None))
        return True

    def reset(self) -> None:
        self.calls.append(("reset", None))

    def set_manual_mode(self, **kwargs: object) -> None:
        self.calls.append(("set_manual_mode", kwargs))

    def start_variation(self, **kwargs: object) -> None:
        self.calls.append(("start_variation", kwargs))

    def disconnect(self) -> None:
        self.calls.append(("disconnect", None))


class LaserControlProtocolCompatibilityTest(unittest.TestCase):
    def setUp(self) -> None:
        _FakeLaserController.instances.clear()

    @staticmethod
    def _kamil_config(laser_payload: dict[str, object]) -> RunConfigModel:
        return RunConfigModel.from_dict(
            {
                "radar": {
                    "model": "kamil_adc",
                    "driver_mode": "native",
                    "laser_control": laser_payload,
                }
            }
        )

    def test_manual_command_matches_device_main_bytes(self) -> None:
        command = Protocol.encode_decode_enable(
            temp1=28.0,
            temp2=28.9,
            current1=33.0,
            current2=35.0,
            pi_coeff1_p=2560,
            pi_coeff1_i=128,
            pi_coeff2_p=2560,
            pi_coeff2_i=128,
            message_id=DEVICE_MAIN_MESSAGE_ID,
        )

        self.assertEqual(command.hex(), DEVICE_MAIN_MANUAL_HEX)

    def test_variation_commands_match_device_main_bytes(self) -> None:
        ld1_command = Protocol.encode_task_enable(
            task_type=TaskType.CHANGE_CURRENT_LD1,
            static_temp1=28.0,
            static_temp2=28.9,
            static_current1=33.0,
            static_current2=35.0,
            min_value=33.0,
            max_value=35.0,
            step=0.05,
            time_step=50,
            delay_time=10,
            message_id=DEVICE_MAIN_MESSAGE_ID,
            pi_coeff1_p=2560,
            pi_coeff1_i=128,
            pi_coeff2_p=2560,
            pi_coeff2_i=128,
        )
        ld2_command = Protocol.encode_task_enable(
            task_type=TaskType.CHANGE_CURRENT_LD2,
            static_temp1=28.0,
            static_temp2=28.9,
            static_current1=33.0,
            static_current2=35.0,
            min_value=33.0,
            max_value=35.0,
            step=0.05,
            time_step=50,
            delay_time=10,
            message_id=DEVICE_MAIN_MESSAGE_ID,
            pi_coeff1_p=2560,
            pi_coeff1_i=128,
            pi_coeff2_p=2560,
            pi_coeff2_i=128,
        )

        self.assertEqual(ld1_command.hex(), DEVICE_MAIN_CHANGE_CURRENT_LD1_HEX)
        self.assertEqual(ld2_command.hex(), DEVICE_MAIN_CHANGE_CURRENT_LD2_HEX)

    def test_start_sequence_matches_device_main_order(self) -> None:
        fake_protocol = _FakeProtocol()
        controller = LaserController(pi_coeff1_p=2560, pi_coeff1_i=128, pi_coeff2_p=2560, pi_coeff2_i=128)
        controller._protocol = fake_protocol

        with patch("python_app.hardware_full.laser_control.controller.time.sleep", return_value=None):
            controller.reset()
            controller.set_manual_mode(
                temp1=28.0,
                temp2=28.9,
                current1=33.0,
                current2=35.0,
                message_id=DEVICE_MAIN_MESSAGE_ID,
            )
            controller.start_variation(
                variation_type=VariationType.CHANGE_CURRENT_LD1,
                params={
                    "static_temp1": 28.0,
                    "static_temp2": 28.9,
                    "static_current1": 33.0,
                    "static_current2": 35.0,
                    "min_value": 33.0,
                    "max_value": 35.0,
                    "step": 0.05,
                    "time_step": 50,
                    "delay_time": 10,
                },
            )

        self.assertEqual(
            [command.hex() for command in fake_protocol.sent],
            [
                "2222",
                DEVICE_MAIN_MANUAL_HEX,
                DEVICE_MAIN_CHANGE_CURRENT_LD1_HEX,
            ],
        )

    def test_stop_sequence_restores_device_main_manual_bytes(self) -> None:
        fake_protocol = _FakeProtocol()
        controller = LaserController(pi_coeff1_p=2560, pi_coeff1_i=128, pi_coeff2_p=2560, pi_coeff2_i=128)
        controller._protocol = fake_protocol

        with patch("python_app.hardware_full.laser_control.controller.time.sleep", return_value=None):
            controller.set_manual_mode(
                temp1=28.0,
                temp2=28.9,
                current1=33.0,
                current2=35.0,
                message_id=DEVICE_MAIN_MESSAGE_ID,
            )
            fake_protocol.sent.clear()
            controller.stop_task(restore_message_id=DEVICE_MAIN_MESSAGE_ID)

        self.assertEqual(
            [command.hex() for command in fake_protocol.sent],
            [
                "2222",
                DEVICE_MAIN_MANUAL_HEX,
            ],
        )

    def test_apply_radar_variation_sequence_matches_device_main_order(self) -> None:
        config = self._kamil_config(
            {
                "enabled": True,
                "port": "/dev/ttyUSB0",
                "mode": "variation",
                "pi_coeff1_p": 2560,
                "pi_coeff1_i": 128,
                "pi_coeff2_p": 2560,
                "pi_coeff2_i": 128,
                "variation": {
                    "variation_type": "CHANGE_CURRENT_LD1",
                    "static_temp1": 28.0,
                    "static_temp2": 28.9,
                    "static_current1": 33.0,
                    "static_current2": 35.0,
                    "min_value": 33.0,
                    "max_value": 35.0,
                    "step": 0.05,
                    "time_step": 50,
                    "delay_time": 10,
                },
            }
        )

        with patch("python_app.hardware_full.laser_control.controller.LaserController", _FakeLaserController):
            applied = apply_kamil_adc_laser_control(config)

        self.assertTrue(applied)
        controller = _FakeLaserController.instances[0]
        self.assertEqual(
            [name for name, _payload in controller.calls],
            ["connect", "reset", "set_manual_mode", "start_variation", "disconnect"],
        )
        manual_payload = controller.calls[2][1]
        self.assertEqual(
            manual_payload,
            {
                "temp1": 28.0,
                "temp2": 28.9,
                "current1": 33.0,
                "current2": 35.0,
                "message_id": DEVICE_MAIN_MESSAGE_ID,
            },
        )

    def test_apply_radar_skips_disabled_laser_control(self) -> None:
        config = self._kamil_config({"enabled": False})

        with patch("python_app.hardware_full.laser_control.controller.LaserController", _FakeLaserController):
            applied = apply_kamil_adc_laser_control(config)

        self.assertFalse(applied)
        self.assertEqual(_FakeLaserController.instances, [])


if __name__ == "__main__":
    unittest.main()
