from __future__ import annotations

from dataclasses import dataclass

from matterlab_serial_device import ScriptedSerialTransport
from matterlab_serial_device.transport import SerialTransportConfig

from matterlab_valves.runze_valve import RunzeMotorEventType, RunzeSelectionValve


@dataclass
class RunzeSelectionSimState:
    address: int
    num_port: int
    current_port: int = 1
    motor_status: RunzeMotorEventType = RunzeMotorEventType.Normal_status
    between_ports_after: int | None = None


class RunzeSelectionSimProtocol:
    def __init__(self, state: RunzeSelectionSimState) -> None:
        self.state = state

    def __call__(self, write_data: bytes, expected: bytes, size: int | None) -> bytes:
        del expected, size
        if len(write_data) < 7:
            return b""
        if write_data[0] != RunzeSelectionValve.STX or write_data[-3] != RunzeSelectionValve.ETX:
            return b""

        body = write_data[1:-3]
        address = body[0]
        command = body[1]
        data_1 = body[2] if len(body) > 2 else 0
        data_2 = body[3] if len(body) > 3 else 0

        if address != self.state.address:
            return b""

        if command == 0x3E:
            return bytes([RunzeSelectionValve.STX, address, RunzeMotorEventType.Normal_status.value, self.state.current_port])

        if command == 0x4A:
            return bytes([RunzeSelectionValve.STX, address, self.state.motor_status.value, self.state.current_port])

        if command == 0x44:
            self._set_port(data_1)
            return bytes([RunzeSelectionValve.STX, address, RunzeMotorEventType.Task_being_executed.value, self.state.current_port])

        if command == 0xA4:
            self._set_port(data_2)
            return bytes([RunzeSelectionValve.STX, address, RunzeMotorEventType.Task_being_executed.value, self.state.current_port])

        if command in {0x45, 0x4F}:
            self.state.current_port = 1
            self.state.between_ports_after = None
            return bytes([RunzeSelectionValve.STX, address, RunzeMotorEventType.Task_being_executed.value, self.state.current_port])

        if command == 0xB4:
            self._validate_port(data_1)
            self.state.between_ports_after = data_1
            return bytes([RunzeSelectionValve.STX, address, RunzeMotorEventType.Task_being_executed.value, data_1])

        if command == 0x00 and len(body) >= 7:
            self.state.address = body[6]
            return bytes([RunzeSelectionValve.STX, address, RunzeMotorEventType.Task_being_executed.value, self.state.current_port])

        if command == 0x02 and len(body) >= 7:
            return bytes([RunzeSelectionValve.STX, address, RunzeMotorEventType.Task_being_executed.value, self.state.current_port])

        if command == 0xFF:
            self.state.current_port = 1
            self.state.motor_status = RunzeMotorEventType.Normal_status
            self.state.between_ports_after = None
            return bytes([RunzeSelectionValve.STX, address, RunzeMotorEventType.Task_being_executed.value, self.state.current_port])

        return bytes([RunzeSelectionValve.STX, address, RunzeMotorEventType.Parameter_error.value, self.state.current_port])

    def _validate_port(self, port: int) -> None:
        if not 1 <= port <= self.state.num_port:
            raise ValueError(f"Port number out of range, default [1, {self.state.num_port}], set to {port}")

    def _set_port(self, port: int) -> None:
        self._validate_port(port)
        self.state.current_port = port
        self.state.between_ports_after = None


def create_runze_selection_sim_transport_factory(
    address: int,
    num_port: int,
    *,
    initial_port: int = 1,
    motor_status: RunzeMotorEventType = RunzeMotorEventType.Normal_status,
):
    state = RunzeSelectionSimState(
        address=address,
        num_port=num_port,
        current_port=initial_port,
        motor_status=motor_status,
    )
    protocol = RunzeSelectionSimProtocol(state)

    def _factory(config: SerialTransportConfig):
        return ScriptedSerialTransport(config, default_response=protocol)

    _factory.state = state
    return _factory
