"""Test pdu."""
from typing import Any

import pytest

import pymodbus.pdu.bit_message as bit_msg
import pymodbus.pdu.diag_message as diag_msg
import pymodbus.pdu.file_message as file_msg
import pymodbus.pdu.mei_message as mei_msg
import pymodbus.pdu.other_message as o_msg
import pymodbus.pdu.register_message as reg_msg
from pymodbus.constants import ModbusStatus
from pymodbus.exceptions import ModbusIOException, NotImplementedException
from pymodbus.pdu import (
    ExceptionResponse,
    ModbusPDU,
)
from pymodbus.pdu.utils import pack_bitstring, unpack_bitstring


class TestPdu:
    """Test modbus PDU."""

    exception = ExceptionResponse(1, 1, 0, 0)

    async def test_error_methods(self):
        """Test all error methods."""
        result = self.exception.encode()
        self.exception.decode(result)
        assert result == b"\x01"
        assert self.exception.exception_code == 1

    async def test_get_pdu_size(self):
        """Test get pdu size."""
        assert not self.exception.get_response_pdu_size()

    async def test_pdu_id(self):
        """Test set illegal pdu id."""
        with pytest.raises(ModbusIOException):
            ModbusPDU(256)

    async def test_is_error(self):
        """Test is_error."""
        assert self.exception.isError()

    def test_calculate_frame_size(self):
        """Test the calculation of Modbus frame sizes."""
        with pytest.raises(NotImplementedException):
            ModbusPDU.calculateRtuFrameSize(b"")
        ModbusPDU.rtu_frame_size = 5
        assert ModbusPDU.calculateRtuFrameSize(b"") == 5
        ModbusPDU.rtu_frame_size = 0
        ModbusPDU.rtu_byte_count_pos = 2
        assert (
            ModbusPDU.calculateRtuFrameSize(
                b"\x11\x01\x05\xcd\x6b\xb2\x0e\x1b\x45\xe6"
            )
            == 0x05 + 5
        )
        assert not ModbusPDU.calculateRtuFrameSize(b"\x11")
        ModbusPDU.rtu_byte_count_pos = 0
        with pytest.raises(NotImplementedException):
            ModbusPDU.calculateRtuFrameSize(b"")
        ModbusPDU.rtu_frame_size = 12
        assert ModbusPDU.calculateRtuFrameSize(b"") == 12
        ModbusPDU.rtu_frame_size = 0
        ModbusPDU.rtu_byte_count_pos = 2
        assert (
            ModbusPDU.calculateRtuFrameSize(
                b"\x11\x01\x05\xcd\x6b\xb2\x0e\x1b\x45\xe6"
            )
            == 0x05 + 5
        )
        ModbusPDU.rtu_byte_count_pos = 0

    # --------------------------
    # Test PDU types generically
    # --------------------------

    requests: list[tuple[type[ModbusPDU], tuple[()], dict[str, Any], bytes]] = [
        (bit_msg.ReadCoilsRequest, (), {"address": 117, "count": 3}, b'\x01\x00\x75\x00\x03'),
        (bit_msg.ReadDiscreteInputsRequest, (), {"address": 117, "count": 3}, b'\x02\x00\x75\x00\x03'),
        (bit_msg.WriteSingleCoilRequest, (), {"address": 117, "bits": [True]}, b'\x05\x00\x75\xff\x00'),
        (bit_msg.WriteMultipleCoilsRequest, (), {"address": 117, "bits": [True, False, True]}, b'\x0f\x00\x75\x00\x03\x01\x05'),
        (diag_msg.DiagnosticBase, (), {"message": None}, b'\x08\x27\x0f'),
        (diag_msg.DiagnosticBase, (), {"message": 0x1010}, b'\x08\x27\x0f\x10\x10'),
        (diag_msg.ReturnQueryDataRequest, (), {"message": b'\x10\x01'}, b'\x08\x00\x00\x10\x01'),
        (diag_msg.RestartCommunicationsOptionRequest, (), {"message": ModbusStatus.ON}, b'\x08\x00\x01\xff\x00'),
        (diag_msg.ReturnDiagnosticRegisterRequest, (), {"message": 0x1010}, b'\x08\x00\x02\x10\x10'),
        (diag_msg.ChangeAsciiInputDelimiterRequest, (), {"message": 0x1010}, b'\x08\x00\x03\x10\x10'),
        (diag_msg.ForceListenOnlyModeRequest, (), {}, b'\x08\x00\x04\x00\x00'),
        (diag_msg.ClearCountersRequest, (), {"message": 0x1010}, b'\x08\x00\n\x10\x10'),
        (diag_msg.ReturnBusMessageCountRequest, (), {"message": 0x1010}, b'\x08\x00\x0b\x10\x10'),
        (diag_msg.ReturnBusCommunicationErrorCountRequest, (), {"message": 0x1010}, b'\x08\x00\x0c\x10\x10'),
        (diag_msg.ReturnBusExceptionErrorCountRequest, (), {"message": 0x1010}, b'\x08\x00\x0d\x10\x10'),
        (diag_msg.ReturnDeviceMessageCountRequest, (), {"message": 0x1010}, b'\x08\x00\x0e\x10\x10'),
        (diag_msg.ReturnDeviceNoResponseCountRequest, (), {"message": 0x1010}, b'\x08\x00\x0f\x10\x10'),
        (diag_msg.ReturnDeviceNAKCountRequest, (), {"message": 0x1010}, b'\x08\x00\x10\x10\x10'),
        (diag_msg.ReturnDeviceBusyCountRequest, (), {"message": 0x1010}, b'\x08\x00\x11\x10\x10'),
        (diag_msg.ReturnDeviceBusCharacterOverrunCountRequest, (), {"message": 0x1010}, b'\x08\x00\x12\x10\x10'),
        (diag_msg.ReturnIopOverrunCountRequest, (), {"message": 0x1010}, b'\x08\x00\x13\x10\x10'),
        (diag_msg.ClearOverrunCountRequest, (), {"message": 0x1010}, b'\x08\x00\x14\x10\x10'),
        (diag_msg.GetClearModbusPlusRequest, (), {"message": 0x1010}, b'\x08\x00\x15\x10\x10'),
        (file_msg.ReadFileRecordRequest, (), {"records": [file_msg.FileRecord(), file_msg.FileRecord()]}, b'\x14\x0e\x06\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00'),
        (file_msg.WriteFileRecordRequest, (), {"records": [file_msg.FileRecord(), file_msg.FileRecord()]}, b'\x15\x0e\x06\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00'),
        (file_msg.ReadFifoQueueRequest, (), {"address": 117}, b'\x18\x00\x75'),
        (mei_msg.ReadDeviceInformationRequest, (), {"read_code": 0x17, "object_id": 0x29}, b'\x2b\x0e\x17\x29'),
        (o_msg.ReadExceptionStatusRequest, (), {}, b'\x07'),
        (o_msg.GetCommEventCounterRequest, (), {}, b'\x0b'),
        (o_msg.GetCommEventLogRequest, (), {}, b'\x0c'),
        (o_msg.ReportDeviceIdRequest, (), {}, b'\x11'),
        (reg_msg.ReadHoldingRegistersRequest, (), {"address": 117, "count": 3}, b'\x03\x00\x75\x00\x03'),
        (reg_msg.ReadInputRegistersRequest, (), {"address": 117, "count": 3}, b'\x04\x00\x75\x00\x03'),
        (reg_msg.ReadWriteMultipleRegistersRequest, (), {"read_address": 17, "read_count": 2, "write_address": 25, "write_registers": [111, 112]}, b'\x17\x00\x11\x00\x02\x00\x19\x00\x02\x04\x00\x6f\x00\x70'),
        (reg_msg.WriteMultipleRegistersRequest, (), {"address": 117, "registers": [111, 121, 131]}, b'\x10\x00\x75\x00\x03\x06\x00\x6f\x00\x79\x00\x83'),
        (reg_msg.WriteSingleRegisterRequest, (), {"address": 117, "registers": [112]}, b'\x06\x00\x75\x00\x70'),
        (reg_msg.MaskWriteRegisterRequest, (), {"address": 0x0104, "and_mask": 0xE1D2, "or_mask": 0x1234}, b'\x16\x01\x04\xe1\xd2\x12\x34'),
    ]

    responses: list[tuple[type[ModbusPDU], tuple[()], dict[str, Any], bytes]] = [
        (bit_msg.ReadCoilsResponse, (), {"bits": [True, True] + [False] * 6, "address": 17}, b'\x01\x01\x03'),
        (bit_msg.ReadDiscreteInputsResponse, (), {"bits": [True, True], "address": 17}, b'\x02\x01\x03'),
        (bit_msg.WriteSingleCoilResponse, (), {"address": 117, "bits": [True]}, b'\x05\x00\x75\xff\x00'),
        (bit_msg.WriteMultipleCoilsResponse, (), {"address": 117, "count": 3}, b'\x0f\x00\x75\x00\x03'),
        (diag_msg.DiagnosticBase, (), {"message": None}, b'\x08\x27\x0f'),
        (diag_msg.DiagnosticBase, (), {"message": 0x1010}, b'\x08\x27\x0f\x10\x10'),
        (diag_msg.ReturnQueryDataResponse, (), {"message": b'AB'}, b'\x08\x00\x00\x41\x42'),
        (diag_msg.RestartCommunicationsOptionResponse, (), {"message": ModbusStatus.ON}, b'\x08\x00\x01\xff\x00'),
        (diag_msg.ReturnDiagnosticRegisterResponse, (), {"message": 0x1010}, b'\x08\x00\x02\x10\x10'),
        (diag_msg.ChangeAsciiInputDelimiterResponse, (), {"message": 0x1010}, b'\x08\x00\x03\x10\x10'),
        (diag_msg.ForceListenOnlyModeResponse, (), {}, b'\x08\x00\x04'),
        (diag_msg.ClearCountersResponse, (), {"message": 0x1010}, b'\x08\x00\n\x10\x10'),
        (diag_msg.ReturnBusMessageCountResponse, (), {"message": 0x1010}, b'\x08\x00\x0b\x10\x10'),
        (diag_msg.ReturnBusCommunicationErrorCountResponse, (), {"message": 0x1010}, b'\x08\x00\x0c\x10\x10'),
        (diag_msg.ReturnBusExceptionErrorCountResponse, (), {"message": 0x1010}, b'\x08\x00\x0d\x10\x10'),
        (diag_msg.ReturnDeviceMessageCountResponse, (), {"message": 0x1010}, b'\x08\x00\x0e\x10\x10'),
        (diag_msg.ReturnDeviceNoResponseCountResponse, (), {"message": 0x1010}, b'\x08\x00\x0f\x10\x10'),
        (diag_msg.ReturnDeviceNAKCountResponse, (), {"message": 0x1010}, b'\x08\x00\x10\x10\x10'),
        (diag_msg.ReturnDeviceBusyCountResponse, (), {"message": 0x1010}, b'\x08\x00\x11\x10\x10'),
        (diag_msg.ReturnDeviceBusCharacterOverrunCountResponse, (), {"message": 0x1010}, b'\x08\x00\x12\x10\x10'),
        (diag_msg.ReturnIopOverrunCountResponse, (), {"message": 0x1010}, b'\x08\x00\x13\x10\x10'),
        (diag_msg.ClearOverrunCountResponse, (), {"message": 0x1010}, b'\x08\x00\x14\x10\x10'),
        (diag_msg.GetClearModbusPlusResponse, (), {"message": 0x1010}, b'\x08\x00\x15\x10\x10'),
        (file_msg.ReadFileRecordResponse, (), {"records": [file_msg.FileRecord(), file_msg.FileRecord()]}, b'\x14\x04\x01\x06\x01\x06'),
        (file_msg.WriteFileRecordResponse, (), {"records": [file_msg.FileRecord(), file_msg.FileRecord()]}, b'\x15\x0e\x06\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00'),
        (file_msg.ReadFifoQueueResponse, (), {"values": [123, 456]}, b'\x18\x00\x06\x00\x02\x00{\x01\xc8'),
        (mei_msg.ReadDeviceInformationResponse, (), {"read_code": 0x17}, b'\x2b\x0e\x17\x83\x00\x00\x00'),
        (o_msg.ReadExceptionStatusResponse, (), {"status": 0x23}, b'\x07\x23'),
        (o_msg.GetCommEventCounterResponse, (), {"count": 123}, b'\x0b\x00\x00\x00\x7b'),
        (o_msg.GetCommEventLogResponse, (), {"status": True, "message_count": 12, "event_count": 7, "events": [12, 14]}, b'\x0c\x08\x00\x00\x00\x07\x00\x0c\x0c\x0e'),
        (o_msg.ReportDeviceIdResponse, (), {"identifier": b'\x12', "status": True}, b'\x11\x02\x12\xff'),
        (reg_msg.ReadHoldingRegistersResponse, (), {"registers": [3, 17]}, b'\x03\x04\x00\x03\x00\x11'),
        (reg_msg.ReadInputRegistersResponse, (), {"registers": [3, 17]}, b'\x04\x04\x00\x03\x00\x11'),
        (reg_msg.ReadWriteMultipleRegistersResponse, (), {"registers": [1, 2]}, b'\x17\x04\x00\x01\x00\x02'),
        (reg_msg.WriteSingleRegisterResponse, (), {"address": 117, "registers": [112]}, b'\x06\x00\x75\x00\x70'),
        (reg_msg.WriteMultipleRegistersResponse, (), {"address": 117, "count": 3}, b'\x10\x00\x75\x00\x03'),
        (reg_msg.MaskWriteRegisterResponse, (), {"address": 0x0104, "and_mask": 0xE1D2, "or_mask": 0x1234}, b'\x16\x01\x04\xe1\xd2\x12\x34'),
    ]

    @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests)
    @pytest.mark.usefixtures("kwargs", "args", "frame")
    def test_pdu_instance(self, pdutype):
        """Test that all PDU types can be created."""
        pdu = pdutype()
        assert pdu

    def test_pdu_base_instance(self):
        """Test that all PDU types can be created."""
        pdu = ModbusPDU(4)
        str(pdu)
        assert pdu
        assert pdu.encode() == b''

    @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests + responses)
    @pytest.mark.usefixtures("frame", "args")
    def test_pdu_instance_args(self, pdutype, kwargs):
        """Test that all PDU types can be created."""
        pdu = pdutype(**kwargs)
        assert pdu
        assert pdutype.__name__ in str(pdu)

    @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests + responses)
    @pytest.mark.usefixtures("frame", "args")
    def test_pdu_instance_extras(self, pdutype, kwargs):
        """Test that all PDU types can be created."""
        tid = 9112
        dev_id = 63
        pdu = pdutype(transaction_id=tid, dev_id=dev_id, **kwargs)
        assert pdu
        assert str(pdu)
        assert pdu.dev_id == dev_id
        assert pdu.transaction_id == tid
        assert pdu.function_code > 0

    def test_pdu_register_as_byte(self):
        """Test verify functions."""
        registers =[int.from_bytes(b'ab', 'big'), int.from_bytes(b'cd', 'big')]
        # NOT ALLOWED, NO conversion.
        req = reg_msg.ReadHoldingRegistersRequest(address=117, registers=registers, count=3)
        assert len(req.registers) == 2
        assert req.registers[0] == 24930
        assert req.registers[1] == 25444

    def test_pdu_verify_address(self):
        """Test verify functions."""
        req = reg_msg.ReadHoldingRegistersRequest(address=10, count=3)
        req.address = -1
        with pytest.raises(ValueError):  # noqa: PT011
            req.verifyAddress()
        req.address = 66000
        with pytest.raises(ValueError):  # noqa: PT011
            req.verifyAddress()

    def test_pdu_verify_count(self):
        """Test verify functions."""
        req = reg_msg.ReadHoldingRegistersRequest(address=2, count=0)
        req.count = 0
        with pytest.raises(ValueError):  # noqa: PT011
            req.verifyCount(100)
        req.count = 101
        with pytest.raises(ValueError):  # noqa: PT011
            req.verifyCount(100)
        with pytest.raises(ValueError):  # noqa: PT011
            req.verifyCount(100, count=0)


    @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests + responses)
    @pytest.mark.usefixtures("args")
    def test_pdu_instance_encode(self, pdutype, kwargs, frame):
        """Test that all PDU types can be created."""
        pdu = pdutype(**kwargs)
        res_frame = pdutype.function_code.to_bytes(1,'big') + pdu.encode()
        assert res_frame == frame

    @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests)
    @pytest.mark.usefixtures("frame", "args")
    def test_get_response_pdu_size2(self, pdutype, kwargs):
        """Test that all PDU types can be created."""
        pdu = pdutype(**kwargs)
        pdu.get_response_pdu_size()
        #FIX size > 0 !!

    @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests + responses)
    @pytest.mark.usefixtures("args")
    def test_pdu_decode(self, pdutype, kwargs, frame):
        """Test pdu decode."""
        pdu = pdutype(**kwargs)
        pdu.decode(frame[1:])

    @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests)
    @pytest.mark.usefixtures("frame", "args")
    async def test_pdu_datastore(self, pdutype, kwargs, mock_server_context):
        """Test that all PDU types can be created."""
        pdu = pdutype(**kwargs)
        assert await pdu.datastore_update(mock_server_context(), 1)

    async def test_pdu_default_datastore(self, mock_server_context):
        """Test that all PDU types can be created."""
        pdu = ModbusPDU()
        with pytest.raises(NotImplementedException):
            assert await pdu.datastore_update(mock_server_context, 1)

    @pytest.mark.parametrize(
        ("bytestream", "bitlist"),
        [
            (b"\x01\x00", [True] + [False] * 15),
            (b"\x00\x80", [False] * 15 + [True]),
            (b"\x00\x01", [False] * 8 + [True] + [False] * 7),
            (b"\x01\x80", [True] + [False] * 14 + [True]),
            (b"\x00\x05", [False] * 8 + [True, False, True] + [False] * 5),
            (b"\x01\x05", [True] + [False] * 7 + [True, False, True] + [False] * 5),
            (b"\x81\x05", [True] + [False] * 6 + [True, True, False, True] + [False] * 5),
            (b"\x00\x01\x81\x05", [False] * 8 + [True] + [False] * 7 + [True] + [False] * 6 + [True, True, False, True] + [False] * 5),

            (b"\x01\x00", [True]),
            (b"\x00\x01", [False] * 8 + [True]),
            (b"\x00\x05", [False] * 8 + [True, False, True]),
            (b"\x01\x05", [True] + [False] * 7 + [True, False, True]),
            (b"\x81\x05", [True] + [False] * 6 + [True, True, False, True]),
            (b"\x00\x01\x81\x05", [False] * 8 + [True] + [False] * 7 + [True] + [False] * 6 + [True, True, False, True]),
        ],
    )
    def test_bit_packing(self, bytestream, bitlist):
        """Test all string <=> bit packing functions."""
        assert pack_bitstring(bitlist, align_byte=False) == bytestream

    @pytest.mark.parametrize(
        ("bytestream", "bitlist"),
        [
            (b"\x01", [True]),
            (b"\x00\x01", [False] * 8 + [True]),
            (b"\x05", [True, False, True]),
            (b"\x01\x05", [True] + [False] * 7 + [True, False, True]),
        ],
    )
    def test_bit_packing8(self, bytestream, bitlist):
        """Test all string <=> bit packing functions."""
        assert pack_bitstring(bitlist) == bytestream

    @pytest.mark.parametrize(
        ("bytestream", "bitlist"),
        [
            (b"\x01", [True] + [False] * 7),
            (b"\x01\x00", [True] + [False] * 15),
            (b"\x00\x01", [False] * 8 + [True] + [False] * 7),
            (b"\x00\x80", [False] * 15 + [True]),
            (b"\x01\x80", [True] + [False] * 14 + [True]),
            (b"\x00\x05", [False] * 8 + [True, False, True] + [False] * 5),
            (b"\x01\x05", [True] + [False] * 7 + [True, False, True] + [False] * 5),
            (b"\x81\x05", [True] + [False] * 6 + [True, True, False, True] + [False] * 5),
            (b"\x05\x81\x01\x00", [True, False, True] + [False] * 5 +
                                  [True] + [False] * 6 + [True] +
                                  [True] + [False] * 7 +
                                  [False] * 8),
        ],
    )
    def test_bit_unpacking(self, bytestream, bitlist):
        """Test all string <=> bit packing functions."""
        assert unpack_bitstring(bytestream) == bitlist

    def test_ExceptionResponse_str(self):
        """Test string conversion of ExceptionResponse."""
        assert str(self.exception) != ""
