"""Test the Jikong BMS implementation."""

import asyncio
from collections.abc import Buffer
from copy import deepcopy
from typing import Final, Literal, cast
from uuid import UUID

from bleak.backends.characteristic import BleakGATTCharacteristic
from bleak.backends.service import BleakGATTService, BleakGATTServiceCollection
from bleak.exc import BleakError
from bleak.uuids import normalize_uuid_str
import pytest

from aiobmsble import BMSInfo, BMSMode, BMSSample
from aiobmsble.basebms import crc_sum, lstr2int
from aiobmsble.bms.jikong_bms import BMS
from tests.bluetooth import generate_ble_device
from tests.conftest import DefGATTChar, MockBleakClient
from tests.test_basebms import BMSBasicTests

BT_FRAME_SIZE = 128

_PROTO_DEFS: Final[dict[str, dict[str, bytearray]]] = {
    "JK02_24S": {
        "dev": bytearray(  # JK02_24S (SW: 10.08)
            b"\x55\xaa\xeb\x90\x03\x79\x4a\x4b\x2d\x42\x32\x41\x32\x30\x53\x32\x30\x50\x00\x00\x00"
            b"\x00\x31\x30\x2e\x58\x47\x00\x00\x00\x31\x30\x2e\x30\x38\x00\x00\x00\xe4\xe7\x6c\x03"
            b"\x11\x00\x00\x00\x4a\x4b\x2d\x42\x4d\x53\x2d\x41\x00\x00\x00\x00\x00\x00\x00\x00\x31"
            b"\x32\x33\x34\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x32\x32\x30\x37\x30\x31"
            b"\x00\x00\x32\x30\x33\x32\x38\x31\x36\x30\x31\x32\x00\x30\x30\x30\x30\x00\x4d\x61\x72"
            b"\x69\x6f\x00\x00\x00\x00\x00\x00\x00\x00\x61\x00\x00\x31\x32\x33\x34\x35\x36\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x93"
        ),
        "ack": bytearray(
            b"\xaa\x55\x90\xeb\xc8\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x44\x41"
            b"\x54\x0d\x0a"
        ),  # ACKnowledge message with attached AT\r\n message (needs to be filtered)
        "cell": bytearray(  # JK02_24S (SW: 10.08)
            b"\x55\xaa\xeb\x90\x02\xc8\xee\x0c\xf2\x0c\xf1\x0c\xf0\x0c\xf0\x0c\xec\x0c\xf0\x0c\xed"
            b"\x0c\xed\x0c\xed\x0c\xed\x0c\xf0\x0c\xf1\x0c\xed\x0c\xee\x0c\xed\x0c\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\xef\x0c\x05\x00\x01"
            b"\x09\x36\x00\x37\x00\x39\x00\x38\x00\x37\x00\x37\x00\x35\x00\x41\x00\x42\x00\x36\x00"
            b"\x37\x00\x3a\x00\x38\x00\x34\x00\x36\x00\x37\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xeb\xce\x00\x00\xc7\x0d\x02\x00"
            b"\x19\x09\x00\x00\xb5\x00\xba\x00\xe4\x00\x00\x00\x02\x00\x00\x38\x5d\xba\x01\x00\x10"
            b"\x15\x03\x00\x3c\x00\x00\x00\xa4\x65\xb9\x00\x64\x00\xd9\x02\x8b\xe8\x6c\x03\x01\x01"
            b"\xb3\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x01\x00\x00\x00\x23"
            b"\x04\x0b\x00\x00\x00\x9f\x19\x40\x40\x00\x00\x00\x00\xe2\x04\x00\x00\x00\x00\x00\x01"
            b"\x00\x03\x00\x00\x83\xd5\x37\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\xbd"
        ),
    },
    "JK02_32S": {  # JK02_32 (SW: V11.48)
        "dev": bytearray(
            b"\x55\xaa\xeb\x90\x03\xa3\x4a\x4b\x5f\x42\x32\x41\x38\x53\x32\x30\x50\x00\x00\x00\x00"
            b"\x00\x31\x31\x2e\x58\x41\x00\x00\x00\x31\x31\x2e\x34\x38\x00\x00\x00\xe4\xa7\x46\x00"
            b"\x07\x00\x00\x00\x31\x32\x76\x34\x32\x30\x61\x00\x00\x00\x00\x00\x00\x00\x00\x00\x31"
            b"\x32\x33\x34\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x32\x34\x30\x37\x30\x34"
            b"\x00\x00\x34\x30\x34\x30\x39\x32\x43\x32\x32\x36\x32\x00\x30\x30\x30\x00\x49\x6e\x70"
            b"\x75\x74\x20\x55\x73\x65\x72\x64\x61\x74\x61\x00\x00\x31\x34\x30\x37\x30\x33\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\xfe\xf9\xff\xff\x1f\x2d\x00\x02\x00\x00\x00\x00\x90\x1f\x00\x00\x00\x00"
            b"\xc0\xd8\xe7\x32\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x04\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x41\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x09\x00\x00\x00\x64\x00\x00\x00\x5f\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xbf\x21\x06\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\xd8"
        ),  # Vendor_ID: JK_B2A8S20P, SN: 404092C2262, HW: V11.XA, SW: V11.48, power-on: 7, Version: 4.28.0
        "ack": bytearray(
            b"\xaa\x55\x90\xeb\xc8\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x44\x41"
            b"\x54\x0d\x0a"
        ),  # ACKnowledge message with attached AT\r\n message (needs to be filtered)
        "cell": bytearray(
            b"\x55\xaa\xeb\x90\x02\xad\xf3\x0c\xf3\x0c\xf3\x0c\xf0\x0c\xf1\x0c\xf0\x0c\xf1\x0c\xf1"
            b"\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\xf2\x0c\x03\x00\x00\x07\x38\x00\x37\x00"
            b"\x36\x00\x37\x00\x36\x00\x37\x00\x36\x00\x37\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x36\x01\x00"
            b"\x00\x00\x00\x8d\x67\x00\x00\x60\xdb\x02\x00\x69\xe4\xff\xff\x1c\x01\x24\x01\x00\x00"
            b"\x00\x00\x00\x00\x00\x44\x80\x2c\x02\x00\x50\x34\x03\x00\x15\x00\x00\x00\xbc\x62\x44"
            b"\x00\x64\x00\x00\x00\x1e\xf3\x68\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\xff\x00\x01\x00\x00\x00\xf1\x03\x00\x00\x23\x00\x29\xb4\x3f\x40\x00"
            b"\x00\x00\x00\x5a\x0a\x00\x00\x00\x01\x00\x01\x00\x06\x00\x00\xef\x3d\x08\x04\x00\x00"
            b"\x00\x00\x36\x01\x00\x00\x00\x00\xf1\x03\x64\x39\x67\x00\x1a\x00\x00\x00\x80\x51\x01"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xfe\xff\x7f\xdc\x0f\x01\x00"
            b"\x80\x03\x00\x00\x00\xb4"
        ),
    },
    "JK02_32S_v15": {  # JK02_32 (SW: V15.38)
        "dev": bytearray(
            b"\x55\xaa\xeb\x90\x03\x21\x4a\x4b\x5f\x50\x42\x32\x41\x31\x36\x53\x32\x30\x50\x00\x00"
            b"\x00\x31\x35\x41\x00\x00\x00\x00\x00\x31\x35\x2e\x33\x38\x00\x00\x00\x20\x48\x01\x00"
            b"\x05\x00\x00\x00\x34\x31\x30\x31\x38\x34\x39\x32\x35\x35\x35\x00\x50\x00\x00\x00\x31"
            b"\x32\x33\x34\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x32\x35\x30\x32\x31\x30"
            b"\x00\x00\x34\x31\x30\x31\x38\x34\x39\x32\x35\x35\x35\x00\x30\x30\x30\x00\x4a\x4b\x2d"
            b"\x42\x4d\x53\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x37\x31\x32\x30\x33\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x4a\x4b\x2d\x42\x4d\x53\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\xfe\xff\xff\xff\x8f\xe9\x1d\x02\x00\x00\x00\x00\x90\x1f\x00\x00\x00\x00"
            b"\xc0\xd8\xe7\xfe\x3f\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xff\x67\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x09\x08\x00\x01\x64\x00\x00\x00\x5f\x00\x00\x00\x3c\x00\x00\x00\x32\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x0e\x00\x00\x0a\x50\x01\x1e\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\x9f\xe9\xff\x0f\x00\x00"
            b"\x00\x00\x00\x00\x00\xf1"
        ),
        "ack": bytearray(
            b"\xaa\x55\x90\xeb\xc8\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x44\x41"
            b"\x54\x0d\x0a"
        ),  # ACKnowledge message with attached AT\r\n message (needs to be filtered)
        "cell": bytearray(
            b"\x55\xaa\xeb\x90\x02\xac\x05\x0d\xfe\x0c\xfe\x0c\x01\x0d\x01\x0d\xfd\x0c\xfb\x0c\x01"
            b"\x0d\xfc\x0c\xfb\x0c\xfe\x0c\xfb\x0c\xf8\x0c\xfb\x0c\xfb\x0c\x09\x0d\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\xff\x0c\x10\x00\x0f\x0c\x40\x00\x3d\x00"
            b"\x40\x00\x3d\x00\x41\x00\x3f\x00\x41\x00\x3e\x00\x41\x00\x3e\x00\x41\x00\x3d\x00\x40"
            b"\x00\x3e\x00\x41\x00\x3f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81\x00\x00"
            b"\x00\x00\x00\xe8\xcf\x00\x00\x4a\xe4\x19\x00\x89\x7c\x00\x00\x86\x00\x80\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x19\x86\xc0\x00\x00\x40\x0d\x03\x00\x09\x00\x00\x00\xb1\x5f\x1c"
            b"\x00\x64\x00\x00\x00\x8c\x4c\x76\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\xff\x00\x01\x00\x00\x00\x05\x04\x52\x00\x00\x00\xd4\x4e\x40\x40\x00"
            b"\x00\x00\x00\xca\x14\x00\x00\x00\x01\x01\x01\x00\x06\x00\x00\xea\x67\x00\x00\x00\x00"
            b"\x00\x00\xcd\x00\xc3\x00\xbf\x00\xbd\x03\x28\x7f\x03\x0a\x1d\x00\x00\x00\x80\x51\x01"
            b"\x00\x00\x00\x01\x00\xa5\x02\x02\x00\x00\x00\x00\x00\x00\xfe\xff\x7f\xdc\x2f\x01\x01"
            b"\xb0\xcf\x07\x00\x00\xbb"
        ),  # 53.224V, 25%, 31.881A, cycles:9, 16.367°C, Float, timer: 677s,
    },
    "JK02_32S_v19.05": {  # JK02_32 (SW: V19.05)
        "dev": bytearray(
            b"\x55\xaa\xeb\x90\x03\x98\x4a\x4b\x5f\x50\x42\x32\x41\x31\x36\x53\x32\x30\x50\x00\x00"
            b"\x00\x31\x39\x41\x00\x00\x00\x00\x00\x31\x39\x2e\x30\x35\x00\x00\x00\x48\x73\x08\x00"
            b"\x0b\x00\x00\x00\x42\x61\x74\x65\x72\x69\x65\x20\x31\x00\x00\x00\x00\x00\x00\x00\x31"
            b"\x32\x33\x34\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x32\x35\x30\x35\x32\x34"
            b"\x00\x00\x35\x30\x33\x32\x31\x34\x38\x34\x39\x30\x30\x30\x36\x34\x33\x00\x4a\x4b\x2d"
            b"\x42\x4d\x53\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x33\x31\x34\x31\x35\x39\x32\x37"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x4a\x4b\x2d\x42\x4d\x53\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\xff\xff\xff\xff\x8f\xe9\x8d\x03\x00\x00\x00\x00\x90\x1f\x00\x00\x00\x00"
            b"\xc0\xd8\xf7\xfe\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x04\xff\xe7\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x09\x08\x00\x01\x64\x00\x00\x00\x5f\x00\x00\x00\x3c\x00\x00\x00\x32\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x0e\x00\x00\x0a\x3c\x01\x1e\x0f\x03\xa4"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\x9f\xe9\xfe\x1b\x00\x00"
            b"\x00\x00\x00\x00\x00\xc5\xaa\x55\x90\xeb\xc8\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x44\x41\x54\x0d\x0a"
        ),  # Vendor_ID: JK_B2A16S20P, SN: 50321489000643, HW: V19.XA, SW: V19.05, power-on: 11
        "ack": bytearray(),  # ACKnowledge is contained at end of dev msg, with AT cmd
        "cell": bytearray(  # copy from v15
            b"\x55\xaa\xeb\x90\x02\xac\x05\x0d\xfe\x0c\xfe\x0c\x01\x0d\x01\x0d\xfd\x0c\xfb\x0c\x01"
            b"\x0d\xfc\x0c\xfb\x0c\xfe\x0c\xfb\x0c\xf8\x0c\xfb\x0c\xfb\x0c\x09\x0d\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\xff\x0c\x10\x00\x0f\x0c\x40\x00\x3d\x00"
            b"\x40\x00\x3d\x00\x41\x00\x3f\x00\x41\x00\x3e\x00\x41\x00\x3e\x00\x41\x00\x3d\x00\x40"
            b"\x00\x3e\x00\x41\x00\x3f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81\x00\x00"
            b"\x00\x00\x00\xe8\xcf\x00\x00\x4a\xe4\x19\x00\x89\x7c\x00\x00\x86\x00\x80\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x19\x86\xc0\x00\x00\x40\x0d\x03\x00\x09\x00\x00\x00\xb1\x5f\x1c"
            b"\x00\x64\x00\x00\x00\x8c\x4c\x76\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\xff\x00\x01\x00\x00\x00\x05\x04\x52\x00\x00\x00\xd4\x4e\x40\x40\x00"
            b"\x00\x00\x00\xca\x14\x00\x00\x00\x01\x01\x01\x00\x06\x00\x00\xea\x67\x00\x00\x00\x00"
            b"\x00\x00\xcd\x00\xc3\x00\xbf\x00\xbd\x03\x28\x7f\x03\x0a\x1d\x00\x00\x00\x80\x51\x01"
            b"\x00\x00\x00\x01\x00\xa5\x02\x02\x00\x00\x00\x00\x00\x00\xfe\xff\x7f\xdc\x2f\x01\x01"
            b"\xb0\xcf\x07\x00\x00\xbb"
        ),  # 53.224V, 25%, 31.881A, cycles:9, 16.367°C, Float, timer: 677s,
    },
    "JK02_32S_v19.27": {  # JK02_32 (SW: V19.27)
        "dev": bytearray(
            b"\x55\xaa\xeb\x90\x03\xda\x4a\x4b\x2d\x50\x42\x32\x41\x31\x36\x53\x32\x30\x50\x00\x00"
            b"\x10\x31\x39\x41\x00\x00\x00\x00\x00\x31\x39\x2e\x32\x37\x00\x00\x00\xc0\x2d\x21\x00"
            b"\x6c\x00\x00\x00\x44\x47\x20\x53\x6d\x61\x72\x74\x20\x42\x4d\x53\x00\x00\x00\x00\x31"
            b"\x35\x35\x33\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x32\x35\x31\x32\x32\x31"
            b"\x00\x00\x35\x31\x30\x32\x30\x42\x4f\x34\x39\x30\x30\x30\x34\x32\x32\x00\x4a\x4b\x2d"
            b"\x42\x4d\x53\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x39\x32\x32\x37\x34\x36\x30\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x4a\x4b\x2d\x42\x4d\x53\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\xff\xff\xff\xff\xaf\xe9\x8d\x0f\x00\x00\x00\x00\x90\x1f\x00\x00\x00\x00"
            b"\xc0\xd8\xf7\xfe\xef\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\xfb\xe6\x00"
            b"\x00\x43\x45\x48\x4d\x50\x52\x54\x00\x00\x00\x00\x00\xff\x0f\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x09\x08\x00\x01\x64\x00\x00\x00\x5f\x00\x00\x00\x3c\x00\x00\x00\x32\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x0e\x00\x00\x05\x32\x01\x1e\x0f\x03\xa4"
            b"\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\xfe\x9f\xeb\xfe\x19\x00\x00"
            b"\x00\x00\x00\x00\x02\xd2"
        ),
        "ack": bytearray(
            b"\xaa\x55\x90\xeb\xc8\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x44"
        ),
        "cell": bytearray(
            b"\x55\xaa\xeb\x90\x02\xda\xec\x0c\xf0\x0c\xf0\x0c\xeb\x0c\xef\x0c\xef\x0c\xf0\x0c\xed"
            b"\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\xee\x0c\x05\x00\x01\x03\x61\x00\x5c\x00"
            b"\x5f\x00\x55\x00\x60\x00\x57\x00\x65\x00\x57\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x01\x00"
            b"\x00\x00\x00\x71\x67\x00\x00\xfe\x2c\x05\x00\x74\xce\xff\xff\xe9\x00\xec\x00\x00\x00"
            b"\x00\x00\xc6\x07\x01\x4e\x48\xba\x03\x00\x90\xca\x04\x00\x0f\x00\x00\x00\xe9\x24\x4a"
            b"\x00\x64\x00\x00\x00\x0f\x2e\x21\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x00\x00\x00\x00\xff\x00\x01\x00\x00\x00\x6f\x24\x00\x00\x67\x00\x1c\xf9\x3e\x40\x00"
            b"\x00\xd8\x00\x58\x0a\x00\x00\x00\x01\x01\x01\x00\x06\x01\x00\x71\x28\x1f\x00\x00\x00"
            b"\x00\x00\x06\x01\xf5\x00\xf0\x00\x01\x00\x92\xb1\x83\x0b\x3b\x04\x00\x00\x80\x51\x01"
            b"\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xff\x7f\xdd\x2f\x01\x01"
            b"\xb0\x0f\x07\x00\x00\x84"
        ),
    },
}

_RESULT_DEFS: Final[dict[str, BMSSample]] = {
    "JK02_24S": {
        "cell_count": 16,
        "delta_voltage": 0.005,
        "temperature": 19.833,
        "voltage": 52.971,
        "current": 2.329,
        "balance_current": 0.002,
        "battery_level": 56,
        "battery_health": 100,
        "cycle_charge": 113.245,
        "cycles": 60,
        "cell_voltages": [
            3.310,
            3.314,
            3.313,
            3.312,
            3.312,
            3.308,
            3.312,
            3.309,
            3.309,
            3.309,
            3.309,
            3.312,
            3.313,
            3.309,
            3.310,
            3.309,
        ],
        "cycle_capacity": 5998.701,
        "design_capacity": 202,
        "power": 123.369,
        "battery_charging": True,
        "temp_values": [18.1, 18.6, 22.8],
        "temp_sensors": 7,
        "problem": False,
        "problem_code": 0,
        "balancer": False,
        "chrg_mosfet": True,
        "dischrg_mosfet": True,
    },
    "JK02_32S": {
        "cell_count": 8,
        "delta_voltage": 0.003,
        "voltage": 26.509,
        "current": -7.063,
        "battery_level": 68,
        "battery_health": 100,
        "cycle_charge": 142.464,
        "design_capacity": 210,
        "cycles": 21,
        "balance_current": 0.0,
        "temp_sensors": 255,
        "problem_code": 0,
        "temp_values": [31.0, 28.4, 29.2, 31.0],
        "cell_voltages": [3.315, 3.315, 3.315, 3.312, 3.313, 3.312, 3.313, 3.313],
        "cycle_capacity": 3776.578,
        "power": -187.233,
        "battery_charging": False,
        "runtime": 72613,
        "temperature": 29.9,
        "problem": False,
        "balancer": False,
        "chrg_mosfet": True,
        "dischrg_mosfet": True,
    },
    "JK02_32S_v15": {
        "cell_count": 16,
        "delta_voltage": 0.016,
        "battery_mode": BMSMode.FLOAT,
        "voltage": 53.224,
        "current": 31.881,
        "battery_level": 25,
        "battery_health": 100,
        "cycle_charge": 49.286,
        "design_capacity": 200,
        "cycles": 9,
        "balance_current": 0.0,
        "temp_sensors": 255,
        "problem_code": 0,
        "temp_values": [12.9, 13.4, 12.8, 20.5, 19.5, 19.1],
        "cell_voltages": [
            3.333,
            3.326,
            3.326,
            3.329,
            3.329,
            3.325,
            3.323,
            3.329,
            3.324,
            3.323,
            3.326,
            3.323,
            3.32,
            3.323,
            3.323,
            3.337,
        ],
        "cycle_capacity": 2623.198,
        "power": 1696.834,
        "battery_charging": True,
        "temperature": 16.367,
        "problem": False,
        "balancer": False,
        "chrg_mosfet": True,
        "dischrg_mosfet": True,
    },
    "JK02_32S_v19.05": {
        "cell_count": 16,
        "delta_voltage": 0.016,
        "battery_mode": BMSMode.FLOAT,
        "voltage": 53.224,
        "current": 31.881,
        "battery_level": 25,
        "battery_health": 100,
        "cycle_charge": 49.286,
        "design_capacity": 200,
        "cycles": 9,
        "balance_current": 0.0,
        "temp_sensors": 255,
        "problem_code": 0,
        "temp_values": [12.9, 13.4, 12.8, 20.5, 19.5, 19.1],
        "cell_voltages": [
            3.333,
            3.326,
            3.326,
            3.329,
            3.329,
            3.325,
            3.323,
            3.329,
            3.324,
            3.323,
            3.326,
            3.323,
            3.32,
            3.323,
            3.323,
            3.337,
        ],
        "cycle_capacity": 2623.198,
        "power": 1696.834,
        "battery_charging": True,
        "temperature": 16.367,
        "problem": False,
        "balancer": False,
        "chrg_mosfet": True,
        "dischrg_mosfet": True,
    },
    "JK02_32S_v19.27": {
        "voltage": 26.481,
        "current": -12.684,
        "problem_code": 0,
        "balance_current": 1.99,
        "balancer": True,
        "battery_level": 78,
        "cycle_charge": 244.296,
        "design_capacity": 314,
        "cycles": 15,
        "battery_health": 100,
        "chrg_mosfet": True,
        "dischrg_mosfet": True,
        "temp_sensors": 255,
        "cell_count": 8,
        "delta_voltage": 0.005,
        "battery_mode": BMSMode.BULK,
        "temp_values": [26.2, 23.3, 23.6, 26.2, 24.5, 24.0],
        "cell_voltages": [3.308, 3.312, 3.312, 3.307, 3.311, 3.311, 3.312, 3.309],
        "cycle_capacity": 6469.202,
        "power": -335.885,
        "battery_charging": False,
        "runtime": 69336,
        "temperature": 24.633,
        "problem": False,
    },
}

_DEV_DEFS: Final[dict[str, BMSInfo]] = {
    "JK02_24S": {
        "model": "JK-B2A20S20P",
        "hw_version": "10.XG",
        "sw_version": "10.08",
        "name": "JK-BMS-A",
        "serial_number": "20328160",
    },
    "JK02_32S": {
        "model": "JK_B2A8S20P",
        "hw_version": "11.XA",
        "sw_version": "11.48",
        "name": "12v420a",
        "serial_number": "404092C2",
    },
    "JK02_32S_v15": {
        "model": "JK_PB2A16S20P",
        "hw_version": "15A",
        "sw_version": "15.38",
        "name": "41018492555",
        "serial_number": "41018492",
    },
    "JK02_32S_v19.05": {
        "model": "JK_PB2A16S20P",
        "hw_version": "19A",
        "sw_version": "19.05",
        "name": "Baterie 1",
        "serial_number": "50321484",
    },
    "JK02_32S_v19.27": {
        "model": "JK-PB2A16S20P",
        "hw_version": "19A",
        "sw_version": "19.27",
        "name": "DG Smart BMS",
        "serial_number": "51020BO4",
    },
}


@pytest.fixture(
    name="protocol_type",
    params=[
        "JK02_24S",
        "JK02_32S",
        "JK02_32S_v15",
        "JK02_32S_v19.05",
        "JK02_32S_v19.27",
    ],
)
def proto(request: pytest.FixtureRequest) -> str:
    """Protocol fixture."""
    assert isinstance(request.param, str)
    return request.param


class TestBasicBMS(BMSBasicTests):
    """Test the basic BMS functionality."""

    bms_class = BMS


class MockJikongBleakClient(MockBleakClient):
    """Emulate a Jikong BMS BleakClient."""

    HEAD_CMD: Final = bytearray(b"\xaa\x55\x90\xeb")
    CMD_INFO: Final = bytearray(b"\x96")
    DEV_INFO: Final = bytearray(b"\x97")
    _FRAME: dict[str, bytearray] = {}

    _task: asyncio.Task[None] | None = None

    def _response(
        self, char_specifier: BleakGATTCharacteristic | int | str | UUID, data: Buffer
    ) -> bytearray:
        frame: Final[bytearray] = bytearray(data)
        if char_specifier != 3 or crc_sum(frame[:-1]) != frame[19]:
            return bytearray()

        if frame.startswith(self.HEAD_CMD + self.CMD_INFO):
            return (
                bytearray(b"\x41\x54\x0d\x0a") + self._FRAME["cell"]
            )  # added AT\r\n command
        if frame.startswith(self.HEAD_CMD + self.DEV_INFO):
            return self._FRAME["dev"]

        return bytearray()

    async def _send_confirm(self) -> None:
        assert self._notify_callback, "send confirm called but notification not enabled"
        await asyncio.sleep(0)
        self._notify_callback(
            "MockJikongBleakClient", self._FRAME.get("ack", bytearray())
        )

    async def write_gatt_char(
        self,
        char_specifier: BleakGATTCharacteristic | int | str | UUID,
        data: Buffer,
        response: bool | None = None,
    ) -> None:
        """Issue write command to GATT."""

        assert (
            self._notify_callback
        ), "write to characteristics but notification not enabled"
        self._notify_callback(
            "MockJikongBleakClient", bytearray(b"\x41\x54\x0d\x0a")
        )  # interleaved AT\r\n command
        resp = self._response(char_specifier, data)
        for notify_data in [
            resp[i : i + BT_FRAME_SIZE] for i in range(0, len(resp), BT_FRAME_SIZE)
        ]:
            self._notify_callback("MockJikongBleakClient", notify_data)
        if bytes(data).startswith(
            self.HEAD_CMD + self.DEV_INFO
        ):  # JK BMS confirms commands with a command in reply
            self._task = asyncio.create_task(self._send_confirm())

    async def disconnect(self) -> None:
        """Mock disconnect and wait for send task."""
        if self._task is not None:
            await asyncio.wait_for(self._task, 0.1)
            assert self._task.done(), "send task still running!"
        await super().disconnect()

    @property
    def services(self) -> BleakGATTServiceCollection:
        """Emulate JiKong BT service setup."""

        serv_col = BleakGATTServiceCollection()
        service = BleakGATTService(None, 1, uuid=normalize_uuid_str("ffe0"))
        service.add_characteristic(
            DefGATTChar(
                service.handle + 1,
                uuid=f"{service.uuid[4:7]!s}1",
                properties=["notify"],
                service=service,
            )
        )
        service.add_characteristic(
            DefGATTChar(
                service.handle + 2,
                uuid=f"{service.uuid[4:7]!s}1",
                properties=["write", "write-without-response"],
                service=service,
            )
        )
        service.add_characteristic(
            DefGATTChar(
                service.handle + 3,
                uuid="0000",
                properties=["write", "write-without-response"],
                service=service,
            )
        )

        serv_col.add_service(service)

        return serv_col


class MockStreamBleakClient(MockJikongBleakClient):
    """Mock JiKong BMS that already sends battery data (no request required)."""

    async def _send_all(self) -> None:
        assert (
            self._notify_callback
        ), "send_all frames called but notification not enabled"
        for resp in self._FRAME.values():
            self._notify_callback("MockJikongBleakClient", resp)
            await asyncio.sleep(0)


class MockWrongBleakClient(MockBleakClient):
    """Mock invalid service for JiKong BMS."""

    @property
    def services(self) -> BleakGATTServiceCollection:
        """Emulate JiKong BT service setup."""

        return BleakGATTServiceCollection()


class MockInvalidBleakClient(MockJikongBleakClient):
    """Emulate a Jikong BMS BleakClient with disconnect error."""

    async def disconnect(self) -> None:
        """Mock disconnect to raise BleakError."""
        if self._task is not None:
            await asyncio.wait_for(self._task, 0.1)
            assert self._task.done(), "send task still running!"
        raise BleakError


class MockOversizedBleakClient(MockJikongBleakClient):
    """Emulate a Jikong BMS BleakClient returning wrong data length."""

    def _response(
        self, char_specifier: BleakGATTCharacteristic | int | str | UUID, data: Buffer
    ) -> bytearray:
        if char_specifier != 3:
            return bytearray()
        if bytearray(data)[0:5] == self.HEAD_CMD + self.CMD_INFO:
            return (  # added AT\r\n command and oversized
                bytearray(b"\x41\x54\x0d\x0a") + self._FRAME["cell"] + bytearray(6)
            )
        if bytearray(data)[0:5] == self.HEAD_CMD + self.DEV_INFO:
            return self._FRAME["dev"] + bytearray(6)  # oversized

        return bytearray()


async def test_update(
    monkeypatch: pytest.MonkeyPatch,
    patch_bleak_client,
    protocol_type: str,
    keep_alive_fixture,
) -> None:
    """Test Jikong BMS data update."""

    monkeypatch.setattr(MockJikongBleakClient, "_FRAME", _PROTO_DEFS[protocol_type])

    patch_bleak_client(MockJikongBleakClient)

    bms = BMS(generate_ble_device(), keep_alive_fixture)

    assert await bms.async_update() == _RESULT_DEFS[protocol_type]

    # query again to check already connected state
    assert await bms.async_update() == _RESULT_DEFS[protocol_type]
    assert bms.is_connected is keep_alive_fixture

    await bms.disconnect()


async def test_device_info(
    monkeypatch: pytest.MonkeyPatch, patch_bleak_client, protocol_type: str
) -> None:
    """Test that the BMS returns initialized dynamic device information."""
    monkeypatch.setattr(MockJikongBleakClient, "_FRAME", _PROTO_DEFS[protocol_type])
    patch_bleak_client(MockJikongBleakClient)
    bms = BMS(generate_ble_device())
    assert await bms.device_info() == _DEV_DEFS[protocol_type]


async def test_hide_temp_sensors(
    monkeypatch: pytest.MonkeyPatch, patch_bleak_client, protocol_type: str
) -> None:
    """Test Jikong BMS data update with not connected temperature sensors."""

    temp12_hide: dict[str, bytearray] = deepcopy(_PROTO_DEFS[protocol_type])

    # clear temp sensor #2
    if protocol_type == "JK02_24S":
        temp12_hide["cell"][182:184] = bytearray(b"\x03\x00")
        temp12_hide["cell"][132:134] = bytearray(b"\x30\xf8")  # -200.0
    else:
        temp12_hide["cell"][214:216] = bytearray(b"\xfb\x00")
        temp12_hide["cell"][162:164] = bytearray(b"\x30\xf8")  # -200.0
    # recalculate CRC
    temp12_hide["cell"][-1] = crc_sum(temp12_hide["cell"][:-1])

    monkeypatch.setattr(MockJikongBleakClient, "_FRAME", temp12_hide)

    patch_bleak_client(MockJikongBleakClient)

    bms = BMS(generate_ble_device())

    # modify result dict to match removed temp#1, temp#2
    ref_result: BMSSample = deepcopy(_RESULT_DEFS[protocol_type])
    if protocol_type == "JK02_24S":
        ref_result |= {"temp_sensors": 3, "temperature": 18.1}
    elif protocol_type == "JK02_32S":
        ref_result |= {"temp_sensors": 251, "temperature": 31.0}
    elif protocol_type in ("JK02_32S_v15", "JK02_32S_v19.05"):
        ref_result |= {"temp_sensors": 251, "temperature": 18.0}
    elif protocol_type == "JK02_32S_v19.27":
        ref_result |= {"temp_sensors": 251, "temperature": 25.225}

    temp_values: list[int | float] = ref_result.get("temp_values", [])
    temp_values.pop(1)  # remove sensor 1
    temp_values.pop(1)  # remove sensor 2
    ref_result["temp_values"] = temp_values.copy()

    assert await bms.async_update() == ref_result

    await bms.disconnect()


async def test_stream_update(
    monkeypatch: pytest.MonkeyPatch,
    patch_bleak_client,
    protocol_type: str,
    caplog: pytest.LogCaptureFixture,
) -> None:
    """Test Jikong BMS data update."""

    _frames: dict[str, bytearray] = deepcopy(_PROTO_DEFS[protocol_type])
    _prot_offset: Literal[-32, 0] = (
        -32 if lstr2int(_frames["dev"][30:38].decode()) < 11 else 0
    )

    monkeypatch.setattr(MockStreamBleakClient, "_FRAME", _frames)
    patch_bleak_client(MockStreamBleakClient)

    bms = BMS(generate_ble_device())

    assert await bms.async_update() == _RESULT_DEFS[protocol_type]
    assert bms._msg_event.is_set() is False, "BMS does not request fresh data"
    assert "requesting cell info" in caplog.text

    _client = cast(MockStreamBleakClient, bms._client)
    _cell_frame: bytearray = _frames["cell"]
    _cell_frame[_prot_offset + 182] = 0x73  # modify data to ensure new read
    _cell_frame[-1] = crc_sum(_cell_frame[:-1])
    caplog.clear()
    await _client._send_all()

    # query again to see if updated streaming data is used
    assert await bms.async_update() == _RESULT_DEFS[protocol_type] | {"cycles": 0x73}
    assert "requesting cell info" not in caplog.text, "BMS did not use streaming data"

    _cell_frame[_prot_offset + 182] = 0x37  # modify data to ensure new read
    _cell_frame[-1] = crc_sum(_cell_frame[:-1])
    caplog.clear()
    # do not automatically send data this time

    # query again to see if BMS recovers if no data is sent
    assert await bms.async_update() == _RESULT_DEFS[protocol_type] | {"cycles": 0x37}
    assert "requesting cell info" in caplog.text, "BMS did not use streaming data"


async def test_invalid_response(
    monkeypatch: pytest.MonkeyPatch, patch_bleak_client, patch_bms_timeout
) -> None:
    """Test data update with BMS returning invalid data."""

    patch_bms_timeout()

    # return type 0x03 (first requested message) with incorrect CRC
    monkeypatch.setattr(
        MockInvalidBleakClient,
        "_response",
        lambda _s, _c, _d: bytearray(b"\x55\xaa\xeb\x90\x03") + bytearray(295),
    )

    patch_bleak_client(MockInvalidBleakClient)

    bms = BMS(generate_ble_device())

    result: BMSSample = {}
    with pytest.raises(TimeoutError):
        result = await bms.async_update()
    assert not result

    await bms.disconnect()


async def test_invalid_frame_type(
    monkeypatch: pytest.MonkeyPatch, patch_bleak_client, patch_bms_timeout
) -> None:
    """Test data update with BMS returning invalid data."""

    patch_bms_timeout()

    monkeypatch.setattr(
        MockInvalidBleakClient,
        "_response",
        lambda _s, _c, _d: bytearray(b"\x55\xaa\xeb\x90\x05")
        + bytearray(295),  # invalid frame type (0x5)
    )

    patch_bleak_client(MockInvalidBleakClient)

    bms = BMS(generate_ble_device())

    result: BMSSample = {}
    with pytest.raises(TimeoutError):
        result = await bms.async_update()
    assert not result

    await bms.disconnect()


async def test_oversized_response(
    monkeypatch: pytest.MonkeyPatch, patch_bleak_client, protocol_type
) -> None:
    """Test data update with BMS returning oversized data, result shall still be ok."""

    monkeypatch.setattr(MockOversizedBleakClient, "_FRAME", _PROTO_DEFS[protocol_type])

    patch_bleak_client(MockOversizedBleakClient)

    bms = BMS(generate_ble_device())

    assert await bms.async_update() == _RESULT_DEFS[protocol_type]

    await bms.disconnect()


async def test_invalid_device(patch_bleak_client) -> None:
    """Test data update with BMS returning invalid data."""

    patch_bleak_client(MockWrongBleakClient)

    bms = BMS(generate_ble_device())

    result: BMSSample = {}

    with pytest.raises(
        ConnectionError, match=r"^Failed to detect characteristics from.*"
    ):
        result = await bms.async_update()

    assert not result

    await bms.disconnect()


async def test_non_stale_data(
    monkeypatch: pytest.MonkeyPatch, patch_bleak_client, patch_bms_timeout
) -> None:
    """Test if BMS class is reset if connection is reset."""

    patch_bms_timeout()

    monkeypatch.setattr(MockJikongBleakClient, "_FRAME", _PROTO_DEFS["JK02_32S"])

    orig_response = MockJikongBleakClient._response
    monkeypatch.setattr(
        MockJikongBleakClient,
        "_response",
        lambda _s, _c, _d: bytearray(b"\x55\xaa\xeb\x90\x05")
        + bytearray(10),  # invalid frame type (0x5)
    )

    patch_bleak_client(MockJikongBleakClient)

    bms = BMS(generate_ble_device())

    # run an update which provides half a valid message and then disconnects
    result: BMSSample = {}
    with pytest.raises(TimeoutError):
        result = await bms.async_update()
    assert not result
    await bms.disconnect()

    # restore working BMS responses and run a test again to see if stale data is kept
    monkeypatch.setattr(MockJikongBleakClient, "_response", orig_response)

    assert await bms.async_update() == _RESULT_DEFS["JK02_32S"]


@pytest.fixture(
    name="problem_response",
    params=[
        (bytearray(b"\x01\x00"), "first_bit"),
        (bytearray(b"\x00\x80"), "last_bit"),
    ],
    ids=lambda param: param[1],
)
def prb_response(request: pytest.FixtureRequest) -> tuple[bytearray, str]:
    """Return faulty response frame."""
    assert isinstance(request.param, tuple)
    return request.param


async def test_problem_response(
    monkeypatch: pytest.MonkeyPatch,
    patch_bleak_client,
    protocol_type: str,
    problem_response: tuple[bytearray, str],
) -> None:
    """Test data update with BMS returning system problem flags."""

    def frame_update(data: bytearray, update: bytearray, pos: int) -> None:
        data[pos : pos + 2] = update
        data[-1] = (int(data[-1]) + sum(update)) & 0xFF

    protocol_def: dict[str, dict[str, bytearray]] = deepcopy(_PROTO_DEFS)
    # set error flags in the copy

    frame_update(
        protocol_def[protocol_type]["cell"],
        problem_response[0],
        136 if protocol_type == "JK02_24S" else 166,
    )

    monkeypatch.setattr(MockJikongBleakClient, "_FRAME", protocol_def[protocol_type])

    patch_bleak_client(MockJikongBleakClient)

    bms = BMS(generate_ble_device(), False)

    assert await bms.async_update() == _RESULT_DEFS[protocol_type] | {
        "problem": True,
        "problem_code": 1 << (0 if problem_response[1] == "first_bit" else 15),
    }

    await bms.disconnect()
