"""Test SimDevice."""

from typing import cast

import pytest

from pymodbus import ModbusDeviceIdentification
from pymodbus.simulator import DataType, SimData, SimDevice
from pymodbus.simulator.simdevice import SimRegs
from pymodbus.simulator.simruntime import SimUtils


class TestSimDevice:
    """Test simulator device config."""

    async def my_action(
        self,
        _function_code: int,
        _start_address: int,
        _address: int,
        _count: int,
        _current_registers: list[int],
        _set_values: list[int] | list[bool] | None,
    ):
        """Run action."""

    def my_sync_action(
        self,
        _function_code: int,
        _start_address: int,
        _address: int,
        _count: int,
        _current_registers: list[int],
        _set_values: list[int] | list[bool] | None,
    ):
        """Run action."""

    simdata1 = SimData(0, datatype=DataType.INT16, values=15)
    simdata2 = SimData(1, datatype=DataType.INT16, values=16)
    simdata3 = SimData(10, datatype=DataType.BITS, values=16)

    @pytest.mark.parametrize(
        "kwargs",
        [
            {"id": 0, "simdata": [SimData(2, datatype=DataType.STRING, values="test")]},
            {"id": 0, "simdata": ([simdata3], [simdata3], [simdata1], [simdata3])},
            {"id": 0, "simdata": simdata2},
            {"id": 0, "simdata": [simdata2, simdata1]},
            {"id": 0, "simdata": [simdata1], "identity": ModbusDeviceIdentification()},
        ],
    )
    def test_simdevice_instanciate(self, kwargs):
        """Test that simdata can be objects."""
        SimDevice(**kwargs)

    @pytest.mark.parametrize(
        "kwargs",
        [
            {"id": 0},
            {"id": 0, "simdata": [simdata1], "identity": 123},
            {"simdata": []},
            {"id": 0, "simdata": (simdata3, [simdata3], [simdata1], [simdata3])},
            {"id": 0, "simdata": ([simdata3], [simdata1], [simdata3])},
            {"id": 0, "simdata": (["not ok"], [simdata3], [simdata1], [simdata3])},
            {"id": 0, "simdata": ([simdata1], [simdata3], [simdata1], [simdata1])},
            {"id": 0, "simdata": ([simdata3], [simdata1], [simdata1], [simdata1])},
            {"id": 0, "simdata": ([simdata3], [simdata3], [simdata1], "not ok")},
            {"id": "not ok", "simdata": [simdata1]},
            {"id": 1.0, "simdata": [simdata1]},
            {"id": 1, "simdata": [simdata1, simdata1]},
            {"id": 256, "simdata": [simdata1]},
            {"id": -1, "simdata": [simdata1]},
            {"id": 1, "simdata": [simdata1], "word_order_big": "hmm"},
            {"id": 1, "simdata": [simdata1], "byte_order_big": "hmm"},
            {"id": 0, "simdata": ["not ok"]},
        ],
    )
    def test_simdevice_not_ok(self, kwargs):
        """Test that simdata can be objects."""
        with pytest.raises(TypeError):
            SimDevice(**kwargs)

    @pytest.mark.parametrize(
        ("block", "expect"),
        [
            ([SimData(0, values=0xFFFF, datatype=DataType.BITS)], 0),
            ([SimData(0, values=[0xFFFF], datatype=DataType.BITS)], 0),
            ([SimData(0, values=[True] * 16, datatype=DataType.BITS)], 0),
            ([SimData(0, values="hello", datatype=DataType.STRING)], 0),
            # (SimData(0), 0),
            ("no valid", 2),
            (["no valid"], 2),
        ],
    )
    def test_simdevice_block(self, block, expect):
        """Test that simdata can be objects."""
        if not expect:
            SimDevice(id=0, simdata=block)
        else:  # expect == 2:
            with pytest.raises(TypeError):
                SimDevice(id=0, simdata=block)

    async def test_simdevice_action(self):
        """Test action."""
        await self.my_action(0, 0, 0, 0, [], None)
        self.my_sync_action(0, 0, 0, 0, [], None)
        SimDevice(1, simdata=[SimData(1)], action=self.my_action)
        SimDevice(
            0,
            simdata=(
                [self.simdata3],
                [self.simdata3],
                [self.simdata1],
                [self.simdata3],
            ),
            action=self.my_action,
        )
        with pytest.raises(TypeError):
            SimDevice(1, simdata=[SimData(1)], action=self.my_sync_action)  # type: ignore[arg-type]
        with pytest.raises(TypeError):
            SimDevice(1, simdata=[SimData(1)], action="no good")  # type: ignore[arg-type]

    async def test_simdevice_bit_addressing(self):
        """Test action."""
        sdblock = ([self.simdata3], [self.simdata3], [self.simdata1], [self.simdata3])
        SimDevice(1, simdata=[SimData(1)], use_bit_addressing=False)
        SimDevice(1, simdata=[SimData(1)], use_bit_addressing=True)
        SimDevice(1, simdata=sdblock, use_bit_addressing=True)
        with pytest.raises(TypeError):
            SimDevice(1, simdata=sdblock, use_bit_addressing=False)

    @pytest.mark.parametrize(
        ("block", "result"),
        [
            (
                [
                    SimData(2, values=125, datatype=DataType.REGISTERS),
                    SimData(1, values=123, datatype=DataType.REGISTERS),
                ],
                (1, [123, 125, 0], [DataType.INT16, DataType.INT16, DataType.INVALID]),
            ),
            (
                [SimData(1, values=123, readonly=True, datatype=DataType.INT16)],
                (
                    1,
                    [123, 0],
                    [DataType.INT16 | SimUtils.RunTimeFlag_READONLY, DataType.INVALID],
                ),
            ),
            (
                [SimData(1, values="ABC", datatype=DataType.STRING)],
                (1, [0x4142, 0x4300, 0], [DataType.STRING, 0, DataType.INVALID]),
            ),
            (
                [
                    SimData(1, values="ABC", datatype=DataType.STRING),
                    SimData(3, values="ABC", datatype=DataType.STRING),
                ],
                (
                    1,
                    [0x4142, 0x4300, 0x4142, 0x4300, 0],
                    [DataType.STRING, 0, DataType.STRING, 0, DataType.INVALID],
                ),
            ),
            (
                [SimData(0, values=0xFFFF, datatype=DataType.BITS)],
                (0, [65535, 0], [DataType.BITS, DataType.INVALID]),
            ),
            (
                [SimData(0, values=[0xFFFF, 0xFFFF], datatype=DataType.BITS)],
                (0, [65535, 65535, 0], [DataType.BITS, 0, DataType.INVALID]),
            ),
            (
                [
                    SimData(
                        0,
                        values=[True] * 2 + [False] * 6 + [False] * 8,
                        datatype=DataType.BITS,
                    )
                ],
                (0, [3, 0], [DataType.BITS, DataType.INVALID]),
            ),
            (
                [
                    SimData(
                        0,
                        values=[True] * 2 + [False] * 6 + [True] + [False] * 6 + [True],
                        datatype=DataType.BITS,
                    )
                ],
                (0, [33027, 0], [DataType.BITS, DataType.INVALID]),
            ),
            (
                [
                    SimData(1, values=123, datatype=DataType.INT16),
                    SimData(3, values=456, datatype=DataType.INT16),
                ],
                (
                    1,
                    [123, 0, 456, 0],
                    [
                        DataType.INT16,
                        DataType.INVALID,
                        DataType.INT16,
                        DataType.INVALID,
                    ],
                ),
            ),
            (
                [
                    SimData(1, values=123, datatype=DataType.REGISTERS),
                    SimData(3, values=456, datatype=DataType.REGISTERS),
                ],
                (
                    1,
                    [123, 0, 456, 0],
                    [
                        DataType.REGISTERS,
                        DataType.INVALID,
                        DataType.REGISTERS,
                        DataType.INVALID,
                    ],
                ),
            ),
            (
                [
                    SimData(1, datatype=DataType.INVALID),
                    SimData(3, datatype=DataType.INVALID),
                ],
                (
                    1,
                    [0, 0, 0, 0],
                    [
                        DataType.INVALID,
                        DataType.INVALID,
                        DataType.INVALID,
                        DataType.INVALID,
                    ],
                ),
            ),
            (
                [
                    SimData(0, values=123, datatype=DataType.INT32),
                    SimData(3, values=456, datatype=DataType.INT32),
                ],
                (
                    0,
                    [0, 123, 0, 0, 456, 0],
                    [
                        DataType.INT32,
                        0,
                        DataType.INVALID,
                        DataType.INT32,
                        0,
                        DataType.INVALID,
                    ],
                ),
            ),
            (
                [
                    SimData(0, values=123, datatype=DataType.INT32),
                    SimData(2, values=456, datatype=DataType.INT32),
                ],
                (
                    0,
                    [0, 123, 0, 456, 0],
                    [DataType.INT32, 0, DataType.INT32, 0, DataType.INVALID],
                ),
            ),
            (
                [
                    SimData(0, values=123, datatype=DataType.UINT32),
                    SimData(3, values=456, datatype=DataType.UINT32),
                ],
                (
                    0,
                    [0, 123, 0, 0, 456, 0],
                    [
                        DataType.UINT32,
                        0,
                        DataType.INVALID,
                        DataType.UINT32,
                        0,
                        DataType.INVALID,
                    ],
                ),
            ),
            (
                [
                    SimData(0, values=27123.5, datatype=DataType.FLOAT32),
                    SimData(3, values=-3.141592, datatype=DataType.FLOAT32),
                ],
                (
                    0,
                    [0x46D3, 0xE700, 0, 0xC049, 0x0FD8, 0],
                    [
                        DataType.FLOAT32,
                        0,
                        DataType.INVALID,
                        DataType.FLOAT32,
                        0,
                        DataType.INVALID,
                    ],
                ),
            ),
            (
                [
                    SimData(0, values=-1234567890123456789, datatype=DataType.INT64),
                    SimData(5, values=1234567890123456789, datatype=DataType.INT64),
                ],
                (
                    0,
                    [
                        0xEEDD,
                        0xEF0B,
                        0x8216,
                        0x7EEB,
                        0,
                        0x1122,
                        0x10F4,
                        0x7DE9,
                        0x8115,
                        0,
                    ],
                    [
                        DataType.INT64,
                        0,
                        0,
                        0,
                        DataType.INVALID,
                        DataType.INT64,
                        0,
                        0,
                        0,
                        DataType.INVALID,
                    ],
                ),
            ),
            (
                [
                    SimData(0, values=1234567890123456789, datatype=DataType.UINT64),
                    SimData(5, values=1234567890123456789, datatype=DataType.UINT64),
                ],
                (
                    0,
                    [
                        0x1122,
                        0x10F4,
                        0x7DE9,
                        0x8115,
                        0,
                        0x1122,
                        0x10F4,
                        0x7DE9,
                        0x8115,
                        0,
                    ],
                    [
                        DataType.UINT64,
                        0,
                        0,
                        0,
                        DataType.INVALID,
                        DataType.UINT64,
                        0,
                        0,
                        0,
                        DataType.INVALID,
                    ],
                ),
            ),
            (
                [
                    SimData(0, values=3.14159265358979, datatype=DataType.FLOAT64),
                    SimData(5, values=-3.14159265358979, datatype=DataType.FLOAT64),
                ],
                (
                    0,
                    [
                        0x4009,
                        0x21FB,
                        0x5444,
                        0x2D11,
                        0,
                        0xC009,
                        0x21FB,
                        0x5444,
                        0x2D11,
                        0,
                    ],
                    [
                        DataType.FLOAT64,
                        0,
                        0,
                        0,
                        DataType.INVALID,
                        DataType.FLOAT64,
                        0,
                        0,
                        0,
                        DataType.INVALID,
                    ],
                ),
            ),
        ],
    )
    def test_simdevice_build(self, block, result):
        """Test build_device() ok."""
        sd = SimDevice(id=1, simdata=block)
        lists = sd.build_device()
        assert cast(tuple, lists)[0] == result[0]
        assert cast(tuple, lists)[1] == result[1]

    def test_simdevice_build_blocks(self):
        """Test build_device() ok."""
        block = (
            [SimData(0, values=123, datatype=DataType.BITS)],
            [SimData(0, values=123, readonly=True, datatype=DataType.BITS)],
            [SimData(0, values=123, datatype=DataType.INT16)],
            [SimData(0, values=123, datatype=DataType.INT16)],
        )
        result = {
            "c": (0, [123, 0], [DataType.BITS, DataType.INVALID]),
            "d": (0, [123, 0], [DataType.BITS, DataType.INVALID]),
            "h": (0, [123, 0], [DataType.INT16, DataType.INVALID]),
            "i": (0, [123, 0], [DataType.INT16, DataType.INVALID]),
        }
        sd = SimDevice(id=1, simdata=block)
        lists = sd.build_device()
        assert lists == result

    @pytest.mark.parametrize(
        ("block", "registers", "addr"),
        [
            ([SimData(0, values=123, datatype=DataType.BITS)], [0x007B, 0x0000], 0),
            ([SimData(0, values=True, datatype=DataType.BITS)], [0x0001, 0x0000], 0),
            (
                [SimData(1, values=123, datatype=DataType.BITS)],
                [0x00F6, 0x0000, 0x0000],
                0,
            ),
            (
                [
                    SimData(0, values=[True] + [False] * 7, datatype=DataType.BITS),
                    SimData(9, values=[True], datatype=DataType.BITS),
                ],
                [0x0201, 0x0000],
                0,
            ),
            (
                [
                    SimData(0, values=[True] + [False] * 7, datatype=DataType.BITS),
                    SimData(10, values=[True], datatype=DataType.BITS),
                ],
                [0x0401, 0x0000],
                0,
            ),
            ([SimData(16, values=123, datatype=DataType.BITS)], [0x007B, 0x0000], 1),
            (
                [SimData(15, values=123, datatype=DataType.BITS)],
                [0x8000, 0x003D, 0x0000],
                0,
            ),
        ],
    )
    def test_simdevice_build_bits(self, block, registers, addr):
        """Test build_device() ok."""
        sim123 = [SimData(addr, values=123, datatype=DataType.INT16)]
        sd = SimDevice(id=1, simdata=(block, block, sim123, sim123))
        reg_len = len(registers) - 2
        result = {
            "c": (
                addr,
                registers,
                [DataType.BITS] + [0] * reg_len + [DataType.INVALID],
            ),
            "d": (
                addr,
                registers,
                [DataType.BITS] + [0] * reg_len + [DataType.INVALID],
            ),
            "h": (addr, [123, 0], [DataType.INT16, DataType.INVALID]),
            "i": (addr, [123, 0], [DataType.INT16, DataType.INVALID]),
        }
        lists = cast(dict[str, SimRegs], sd.build_device())
        assert lists == result

    @pytest.mark.parametrize("count", range(1, 4))
    @pytest.mark.parametrize("data_count", range(1, 4))
    def test_simdevice_build_count(self, count, data_count):
        """Test build_device() ok."""
        list_data = []
        start_addr = 1
        regs = [123]
        flags = [DataType.REGISTERS]
        for _ in range(data_count):
            list_data.append(
                SimData(
                    start_addr, count=count, values=123, datatype=DataType.REGISTERS
                )
            )
            start_addr += count
        sd = SimDevice(id=1, simdata=list_data)
        lists = sd.build_device()
        result = (
            1,
            regs * count * data_count + [0],
            flags * count * data_count + [DataType.INVALID],
        )
        assert lists == result
