"""
Parser for Govee BLE advertisements.

This file is shamelessly copied from the following repository:
https://github.com/Ernst79/bleparser/blob/c42ae922e1abed2720c7fac993777e1bd59c0c93/package/bleparser/govee.py

MIT License applies.
"""

from __future__ import annotations

import logging
import struct
from dataclasses import dataclass
from enum import Enum

from bluetooth_data_tools import short_address
from bluetooth_sensor_state_data import BluetoothData
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from home_assistant_bluetooth import BluetoothServiceInfo
from sensor_state_data import BinarySensorDeviceClass, SensorLibrary

_LOGGER = logging.getLogger(__name__)


PACKED_hHB_LITTLE = struct.Struct("<hHB")
PACKED_hHB = struct.Struct(">hHB")
PACKED_hh = struct.Struct(">hh")
PACKED_hhhchhh_LITTLE = struct.Struct("<hhhchhh")
PACKED_H = struct.Struct(">H")

PACKED_hhhh = struct.Struct(">hhhh")
PACKED_hhbhh = struct.Struct(">hhbhh")
PACKED_hhhhh = struct.Struct(">hhhhh")
PACKED_hhhhhh = struct.Struct(">hhhhhh")


ERROR = "error"

MIN_TEMP = -40
MAX_TEMP = 100

NOT_GOVEE_MANUFACTURER = {76}

PROBE_MAPPING_1_2 = [1, 2]
PROBE_MAPPING_3_4 = [3, 4]

FOUR_PROBES_MAPPING = dict.fromkeys(
    (1, 65, 129, 193), PROBE_MAPPING_1_2
) | dict.fromkeys((2, 66, 130, 194), PROBE_MAPPING_3_4)

SIX_PROBES_MAPPING = {
    0: [[1, 0x01], [2, 0x02]],
    64: [[3, 0x04], [4, 0x08]],
    128: [[5, 0x10], [6, 0x20]],
}


def decode_temp_humid_battery_error(data: bytes) -> tuple[float, float, int, bool]:
    temp, humi = decode_temp_humid(data[0:3])
    batt = int(data[-1] & 0x7F)
    err = bool(data[-1] & 0x80)
    return temp, humi, batt, err


def decode_temp_humid(temp_humid_bytes: bytes) -> tuple[float, float]:
    """Decode potential negative temperatures."""
    base_num = (
        (temp_humid_bytes[0] << 16) + (temp_humid_bytes[1] << 8) + temp_humid_bytes[2]
    )
    is_negative = base_num & 0x800000
    temp_as_int = base_num & 0x7FFFFF
    temp_as_float = int(temp_as_int / 1000) / 10.0
    if is_negative:
        temp_as_float = -temp_as_float
    humid = (temp_as_int % 1000) / 10.0
    return temp_as_float, humid


def decode_temps_probes(packet_value: int) -> float:
    """Filter potential negative temperatures."""
    if packet_value < 0:
        return 0.0
    return float(packet_value / 100)


def decode_temps_probes_none(packet_value: int) -> float | None:
    """Filter potential negative temperatures."""
    if packet_value < 0:
        return None
    return float(packet_value)


def hex(data: bytes) -> str:
    """Return a string object containing two hexadecimal digits for each byte in the instance."""
    return "b'{}'".format("".join(f"\\x{b:02x}" for b in data))


def decode_temps_from_4_bytes(packet_value: int) -> float:
    """Decode temperature values (to one decimal place)."""
    if packet_value & 0x80000000:
        # Handle freezing temperatures
        packet_value &= 0x7FFFFFFF
        return float(int(packet_value / -10000000) / -10)
    return float(int(packet_value / 1000000) / 10)


def decode_humi_from_4_bytes(packet_value: int) -> float:
    """Decode humidity values (to one decimal place)"""
    packet_value &= 0x7FFFFFFF
    return float(int((packet_value % 1000000) / 1000) / 10)


def decode_pm25_from_4_bytes(packet_value: int) -> int:
    """Decode humidity values"""
    packet_value &= 0x7FFFFFFF
    return int(packet_value % 1000)


def calculate_crc(data: bytes) -> int:
    crc = 0x1D0F
    for b in data:
        for s in range(7, -1, -1):
            mask = 0
            if (crc >> 15) ^ (b >> s) & 1:
                mask = 0x1021
            crc = ((crc << 1) ^ mask) & 0xFFFF
    return crc


def decrypt_data(key: bytes, data: bytes) -> bytes:
    cipher = Cipher(algorithms.AES(key[::-1]), modes.ECB())
    decryptor = cipher.decryptor()
    return (decryptor.update(data[::-1]) + decryptor.finalize())[::-1]


class SensorType(Enum):
    THERMOMETER = "thermometer"
    BUTTON = "button"
    MOTION = "motion"
    WINDOW = "window"
    VIBRATION = "vibration"
    PRESENCE = "presence"
    PRESSURE = "pressure"


@dataclass
class ModelInfo:
    """Model information for Govee sensors."""

    model_id: str
    sensor_type: SensorType
    button_count: int
    sleepy: bool


_MODEL_DB = {
    "H5121": ModelInfo("H5121", SensorType.MOTION, 0, True),
    "H5122": ModelInfo("H5122", SensorType.BUTTON, 1, True),
    "H5123": ModelInfo("H5123", SensorType.WINDOW, 0, True),
    "H5124": ModelInfo("H5124", SensorType.VIBRATION, 0, True),
    "H5125": ModelInfo("H5125", SensorType.BUTTON, 6, True),
    "H5126": ModelInfo("H5126", SensorType.BUTTON, 2, True),
    "H5127": ModelInfo("H5127", SensorType.PRESENCE, 0, True),
    "H5130": ModelInfo("H5130", SensorType.PRESSURE, 1, True),
}


def get_model_info(model_id: str) -> ModelInfo:
    """Get model information for a Govee sensor."""
    return _MODEL_DB.get(
        model_id, ModelInfo(model_id, SensorType.THERMOMETER, 0, False)
    )


class GoveeBluetoothDeviceData(BluetoothData):
    """Data for Govee BLE sensors."""

    def _start_update(self, service_info: BluetoothServiceInfo) -> None:
        """Update from BLE advertisement data."""
        _LOGGER.debug("Parsing Govee BLE advertisement data: %s", service_info)
        manufacturer_data = service_info.manufacturer_data
        service_uuids = service_info.service_uuids
        local_name = service_info.name
        address = service_info.address
        self.set_device_manufacturer("Govee")

        if local_name.startswith("Govee_"):
            self.set_device_name(service_info.name[6:].replace("_", " "))

        if local_name.startswith("GV"):
            self.set_device_name(service_info.name[2:].replace("_", " "))
        self.set_precision(2)

        for mfr_id, mfr_data in manufacturer_data.items():
            if mfr_id in NOT_GOVEE_MANUFACTURER:
                continue
            self._process_mfr_data(address, local_name, mfr_id, mfr_data, service_uuids)

    @property
    def device_type(self) -> str | None:
        """Return the device type."""
        primary_device_id = self.primary_device_id
        if device_type := self._device_id_to_type.get(primary_device_id):
            return device_type.partition("-")[0]
        return None

    @property
    def button_count(self) -> int:
        """Return the number of buttons on the device."""
        device_type = self.device_type
        assert device_type is not None
        return get_model_info(device_type).button_count

    @property
    def sensor_type(self) -> SensorType:
        """Return the sensor type."""
        device_type = self.device_type
        assert device_type is not None
        return get_model_info(device_type).sensor_type

    @property
    def sleepy(self) -> bool:
        """Return if the device is sleep."""
        device_type = self.device_type
        assert device_type is not None
        return get_model_info(device_type).sleepy

    def _process_mfr_data(
        self,
        address: str,
        local_name: str,
        mgr_id: int,
        data: bytes,
        service_uuids: list[str],
    ) -> None:
        """Parser for Govee sensors."""
        if debug_logging := _LOGGER.isEnabledFor(logging.DEBUG):
            _LOGGER.debug("Parsing Govee sensor: %s %s", mgr_id, hex(data))
        msg_length = len(data)
        if msg_length > 25 and b"INTELLI_ROCKS" in data:
            # INTELLI_ROCKS sometimes ends up glued on to the end of the message
            data = data[:-25]
            msg_length = len(data)
            if debug_logging:
                _LOGGER.debug("Cleaned up packet: %s %s", mgr_id, data.hex())

        if msg_length == 24:
            time_ms = data[2:6]
            enc_data = data[6:22]
            enc_crc = data[22:24]
            if not calculate_crc(enc_data) == int.from_bytes(enc_crc, "big"):
                _LOGGER.debug("CRC check failed for H512/3x: %s", hex(data))
                return

            key = time_ms + bytes(12)
            try:
                decrypted = decrypt_data(key, enc_data)
            except ValueError:
                _LOGGER.warning("Failed to decrypt H512/3x: %s", hex(data))
                return
            _LOGGER.debug("Decrypted H512/3x: %s - %s", decrypted, decrypted.hex())
            model_id = decrypted[2]
            # GV5121
            # 01040302640100000000000000000000

            # GV5122
            # 01050802640000000000000000000000
            # 01050802640000000000000000000000

            # GV5123
            # 01050202640200000000000000000000

            # GV5124
            # 01030902640100000000000000000000

            # GV5125
            # 01010a02640000000000000000000000

            # GV5126
            # 01010b02640100000000000000000000

            # GV5130
            # 010e0d02640000000000000000000000
            sensor_type = SensorType.BUTTON
            if "GV5121" in local_name or model_id == 3:
                self.set_device_type("H5121")
                self.set_device_name(f"5121{short_address(address)}")
                sensor_type = SensorType.MOTION
            elif "GV5122" in local_name or model_id == 8:
                self.set_device_type("H5122")
                self.set_device_name(f"5122{short_address(address)}")
            elif "GV5123" in local_name or model_id == 2:
                self.set_device_type("H5123")
                self.set_device_name(f"5123{short_address(address)}")
                sensor_type = SensorType.WINDOW
            elif "GV5124" in local_name or model_id == 9:
                self.set_device_type("H5124")
                self.set_device_name(f"5124{short_address(address)}")
                sensor_type = SensorType.VIBRATION
            elif "GV5125" in local_name or model_id == 10:
                self.set_device_type("H5125")
                self.set_device_name(f"5125{short_address(address)}")
            elif "GV5126" in local_name or model_id == 11:
                self.set_device_type("H5126")
                self.set_device_name(f"5126{short_address(address)}")
            elif "GV5130" in local_name or model_id == 13:
                self.set_device_type("H5130")
                self.set_device_name(f"5130{short_address(address)}")
                sensor_type = SensorType.PRESSURE
            else:
                return

            battery_percentage = decrypted[4]
            button_number_pressed = decrypted[5]
            self.update_predefined_sensor(
                SensorLibrary.BATTERY__PERCENTAGE, battery_percentage
            )
            if sensor_type is SensorType.WINDOW:
                # H5123 is a door/window sensor
                self.update_predefined_binary_sensor(
                    BinarySensorDeviceClass.WINDOW, button_number_pressed == 2
                )
            elif sensor_type is SensorType.VIBRATION:
                # H5124 is a vibration sensor
                if button_number_pressed == 1:
                    self.fire_event("vibration", "vibration")
            elif sensor_type is SensorType.MOTION:
                if button_number_pressed == 1:
                    self.fire_event("motion", "motion")
            elif sensor_type is SensorType.PRESSURE:
                # H5130 is a pressure sensor
                if button_number_pressed == 16:
                    self.fire_event("button_0", "press")
                else:
                    self.update_predefined_binary_sensor(
                        BinarySensorDeviceClass.PRESENCE, button_number_pressed == 1
                    )
            else:
                self.fire_event(f"button_{button_number_pressed}", "press")
            return

        if msg_length == 6 and (
            (data.startswith(b"\xec\x00\x01\x01") and "H5127" in local_name)
            or mgr_id == 0x8803
        ):
            self.set_device_type("H5127")
            self.set_device_name(f"H5127{short_address(address)}")
            present = data[4] == 1
            motion = data[5] == 17
            self.update_predefined_binary_sensor(
                BinarySensorDeviceClass.OCCUPANCY, present
            )
            self.update_predefined_binary_sensor(BinarySensorDeviceClass.MOTION, motion)

        if msg_length == 6 and (
            (is_5072 := "H5072" in local_name)
            or (is_5075 := "H5075" in local_name)
            or mgr_id == 0xEC88
        ):
            if is_5072:
                self.set_device_type("H5072")
            elif is_5075:
                self.set_device_type("H5075")
            else:
                self.set_device_type("H5072/H5075")
            temp, humi, batt, err = decode_temp_humid_battery_error(data[1:5])
            if temp >= MIN_TEMP and temp <= MAX_TEMP and not err:
                self.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, temp)
                self.update_predefined_sensor(SensorLibrary.HUMIDITY__PERCENTAGE, humi)
            else:
                _LOGGER.debug(
                    "Ignoring invalid sensor values, temperature: %.1f, humidity: %.1f, error: %s",
                    temp,
                    humi,
                    err,
                )
                self.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, ERROR)
                self.update_predefined_sensor(SensorLibrary.HUMIDITY__PERCENTAGE, ERROR)
            self.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, batt)
            return

        if msg_length == 8 and ("GV5112" in local_name or "H5112" in local_name):
            self.set_device_type("H5112")
            self.set_device_name(f"H5112 {short_address(address)}")

            # Last byte specifies the probe id: 41 for probe 1, 82 for probe 2
            probe_id = 0
            if data[7] == 0x41:
                probe_id = 1
            elif data[7] == 0x82:
                probe_id = 2
            else:
                _LOGGER.debug(
                    "Unknown probe id: %s for a H5112, data: %s",
                    data[7],
                    hex(data),
                )
                return

            temp, humi, batt, err = decode_temp_humid_battery_error(data[2:6])
            if MIN_TEMP <= temp <= MAX_TEMP and not err:
                self.update_temp_probe(temp, probe_id)
                if probe_id == 1:
                    self.update_humidity_probe(humi, probe_id)
            else:
                _LOGGER.debug(
                    "Ignoring invalid sensor values, probe: %d, temperature: %.1f, humidity: %.1f, error: %s",
                    probe_id,
                    temp,
                    humi,
                    err,
                )
                self.update_temp_probe(ERROR, probe_id)
                if probe_id == 1:
                    self.update_humidity_probe(ERROR, probe_id)

            self.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, batt)
            return

        if msg_length == 8 and "5140" in local_name:
            self.set_device_type("H5140")
            temp, humi = decode_temp_humid(data[2:5])
            (co2,) = PACKED_H.unpack(data[5:7])
            err = bool(data[7])
            if MIN_TEMP <= temp <= MAX_TEMP and not err:
                self.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, temp)
                self.update_predefined_sensor(SensorLibrary.HUMIDITY__PERCENTAGE, humi)
                self.update_predefined_sensor(
                    SensorLibrary.CO2__CONCENTRATION_PARTS_PER_MILLION, co2
                )
            else:
                _LOGGER.debug(
                    "Ignoring invalid sensor values, temperature: %.1f, humidity: %.1f, co2: %d, error: %s",
                    temp,
                    humi,
                    co2,
                    err,
                )
                self.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, ERROR)
                self.update_predefined_sensor(SensorLibrary.HUMIDITY__PERCENTAGE, ERROR)
                self.update_predefined_sensor(
                    SensorLibrary.CO2__CONCENTRATION_PARTS_PER_MILLION, ERROR
                )
            return

        if msg_length in (6, 8) and (
            (is_5108 := "H5108" in local_name)
            or (is_5100 := "H5100" in local_name)
            or (is_5101 := "H5101" in local_name)
            or (is_5102 := "H5102" in local_name)
            or (is_5103 := "H5103" in local_name)
            or (is_5104 := "H5104" in local_name)
            or (is_5105 := "H5105" in local_name)
            or (is_5174 := "H5174" in local_name)
            or (is_5177 := "H5177" in local_name)
            or (is_5110 := "H5110" in local_name)
            or (is_5179 := "GV5179" in local_name)
            or (mgr_id == 0x0001 and msg_length == 8)
        ):
            if is_5108 or msg_length == 8:
                self.set_device_type("H5108")
            elif is_5100:
                self.set_device_type("H5100")
            elif is_5101:
                self.set_device_type("H5101")
            elif is_5102:
                self.set_device_type("H5102")
            elif is_5103:
                self.set_device_type("H5103")
            elif is_5104:
                self.set_device_type("H5104")
            elif is_5105:
                self.set_device_type("H5105")
            elif is_5174:
                self.set_device_type("H5174")
            elif is_5177:
                self.set_device_type("H5177")
            elif is_5110:
                self.set_device_type("H5110")
            elif is_5179:
                self.set_device_type("GV5179")
            else:
                self.set_device_type("H5101/H5102/H5104/H5108/H5174/H5177/GV5179")
            temp, humi, batt, err = decode_temp_humid_battery_error(data[2:6])
            if temp >= MIN_TEMP and temp <= MAX_TEMP and not err:
                self.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, temp)
                self.update_predefined_sensor(SensorLibrary.HUMIDITY__PERCENTAGE, humi)
            else:
                _LOGGER.debug(
                    "Ignoring invalid sensor values, temperature: %.1f, humidity: %.1f, error: %s",
                    temp,
                    humi,
                    err,
                )
                self.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, ERROR)
                self.update_predefined_sensor(SensorLibrary.HUMIDITY__PERCENTAGE, ERROR)
            self.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, batt)
            return

        if msg_length == 7 and ("H5074" in local_name or mgr_id == 0xEC88):
            self.set_device_type("H5074")
            (temp, humi, batt) = PACKED_hHB_LITTLE.unpack(data[1:6])
            self.update_predefined_sensor(
                SensorLibrary.TEMPERATURE__CELSIUS, temp / 100
            )
            self.update_predefined_sensor(
                SensorLibrary.HUMIDITY__PERCENTAGE, humi / 100
            )
            self.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, batt)
            return

        if msg_length == 9 and (
            mgr_id == 0xEC88
            or "H5051" in local_name
            or "H5052" in local_name
            or "H5071" in local_name
        ):
            if "H5071" in local_name:
                self.set_device_type("H5071")
            elif "H5052" in local_name:
                self.set_device_type("H5052")
            else:
                self.set_device_type("H5051")
                self.set_device_name(f"H5051 {short_address(address)}")
            (temp, humi, batt) = PACKED_hHB_LITTLE.unpack(data[1:6])
            self.update_predefined_sensor(
                SensorLibrary.TEMPERATURE__CELSIUS, temp / 100
            )
            self.update_predefined_sensor(
                SensorLibrary.HUMIDITY__PERCENTAGE, humi / 100
            )
            self.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, batt)
            return

        if msg_length == 9 and (
            "H5178" in local_name or "B5178" in local_name or mgr_id == 0x0001
        ):
            temp, humi, batt, err = decode_temp_humid_battery_error(data[3:7])
            sensor_id = data[2]
            device_id = "primary"
            if local_name.startswith("H5178") or local_name.startswith("B5178"):
                self.set_title(local_name)
            else:
                self.set_title("H5178")
            if sensor_id == 0:
                self.set_device_name(
                    f"{local_name} Primary".replace("_", " "), device_id
                )
                self.set_device_type("H5178", device_id)
                self.set_device_manufacturer("Govee", device_id)
            elif sensor_id == 1:
                device_id = "remote"
                self.set_device_name(
                    f"{local_name} Remote".replace("_", " "), device_id
                )
                self.set_device_type("H5178-REMOTE", device_id)
                self.set_device_manufacturer("Govee", device_id)
            elif debug_logging:
                _LOGGER.debug(
                    "Unknown sensor id for Govee H5178,"
                    " please report to the developers, data: %s",
                    hex(data),
                )
            if temp >= MIN_TEMP and temp <= MAX_TEMP and not err:
                self.update_predefined_sensor(
                    SensorLibrary.TEMPERATURE__CELSIUS, temp, device_id=device_id
                )
                self.update_predefined_sensor(
                    SensorLibrary.HUMIDITY__PERCENTAGE, humi, device_id=device_id
                )
            else:
                _LOGGER.debug(
                    "Ignoring invalid sensor values, temperature: %.1f, humidity: %.1f, error: %s",
                    temp,
                    humi,
                    err,
                )
                self.update_predefined_sensor(
                    SensorLibrary.TEMPERATURE__CELSIUS, ERROR, device_id=device_id
                )
                self.update_predefined_sensor(
                    SensorLibrary.HUMIDITY__PERCENTAGE, ERROR, device_id=device_id
                )
            self.update_predefined_sensor(
                SensorLibrary.BATTERY__PERCENTAGE, batt, device_id=device_id
            )
            return

        if msg_length == 9 and ("H5179" in local_name or mgr_id == 0x8801):
            self.set_device_type("H5179")
            temp, humi, batt = PACKED_hHB_LITTLE.unpack(data[4:9])
            self.update_predefined_sensor(
                SensorLibrary.TEMPERATURE__CELSIUS, temp / 100
            )
            self.update_predefined_sensor(
                SensorLibrary.HUMIDITY__PERCENTAGE, humi / 100
            )
            self.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, batt)
            return

        if msg_length == 14 and (
            "H5181" in local_name
            or mgr_id in {0xF861, 0x388A, 0xEA42, 0xAAA2, 0xD14B}
            or "00008151-0000-1000-8000-00805f9b34fb" in service_uuids
        ):
            self.set_device_type("H5181")
            self.set_device_name(f"H5181 {short_address(address)}")
            (temp_probe_1, temp_alarm_1) = PACKED_hh.unpack(data[8:12])
            self.update_temp_probe_with_alarm(
                decode_temps_probes(temp_probe_1), decode_temps_probes(temp_alarm_1), 1
            )
            return

        if msg_length == 17 and (
            "H5182" in local_name
            or mgr_id == 0x2730
            or "00008251-0000-1000-8000-00805f9b34fb" in service_uuids
        ):
            self.set_device_type("H5182")
            self.set_device_name(f"H5182 {short_address(address)}")
            (
                temp_probe_1,
                temp_alarm_1,
                _,
                temp_probe_2,
                temp_alarm_2,
            ) = PACKED_hhbhh.unpack(data[8:17])
            self.update_temp_probe_with_alarm(
                decode_temps_probes(temp_probe_1), decode_temps_probes(temp_alarm_1), 1
            )
            self.update_temp_probe_with_alarm(
                decode_temps_probes(temp_probe_2), decode_temps_probes(temp_alarm_2), 2
            )
            return

        if msg_length == 14 and (
            "H5183" in local_name
            or mgr_id in {0x67DD, 0xE02F, 0xF79F}
            or "00008351-0000-1000-8000-00805f9b34fb" in service_uuids
        ):
            self.set_device_type("H5183")
            self.set_device_name(f"H5183 {short_address(address)}")
            (temp_probe_1, temp_alarm_1) = PACKED_hh.unpack(data[8:12])
            self.update_temp_probe_with_alarm(
                decode_temps_probes(temp_probe_1), decode_temps_probes(temp_alarm_1), 1
            )
            return

        if msg_length == 17 and (
            "H5184" in local_name
            or mgr_id == 0x1B36
            or "00008451-0000-1000-8000-00805f9b34fb" in service_uuids
        ):
            sensor_id = data[6]
            self.set_device_type("H5184")
            self.set_device_name(f"H5184 {short_address(address)}")
            (
                temp_probe_first,
                temp_alarm_first,
                _,
                temp_probe_second,
                temp_alarm_second,
            ) = PACKED_hhbhh.unpack(data[8:17])
            if not (ids := FOUR_PROBES_MAPPING.get(sensor_id)):
                if debug_logging:
                    _LOGGER.debug(
                        "Unknown sensor id: %s for a H5184, data: %s",
                        sensor_id,
                        hex(data),
                    )
                return
            self.update_temp_probe_with_alarm(
                decode_temps_probes(temp_probe_first),
                decode_temps_probes(temp_alarm_first),
                ids[0],
            )
            self.update_temp_probe_with_alarm(
                decode_temps_probes(temp_probe_second),
                decode_temps_probes(temp_alarm_second),
                ids[1],
            )
            return

        if msg_length == 20 and (
            "H5185" in local_name
            or mgr_id in (0x4A32, 0x332, 0x4C32)
            or "00008551-0000-1000-8000-00805f9b34fb" in service_uuids
        ):
            self.set_device_type("H5185")
            self.set_device_name(f"H5185 {short_address(address)}")
            (
                temp_probe_1,
                temp_alarm_1,
                _,
                temp_probe_2,
                temp_alarm_2,
            ) = PACKED_hhhhh.unpack(data[8:18])
            self.update_temp_probe_with_alarm(
                decode_temps_probes(temp_probe_1), decode_temps_probes(temp_alarm_1), 1
            )
            self.update_temp_probe_with_alarm(
                decode_temps_probes(temp_probe_2), decode_temps_probes(temp_alarm_2), 2
            )
            return

        if msg_length == 20 and (
            "H5191" in local_name
            or mgr_id == 0xAC63
            or "00009151-0000-1000-8000-00805f9b34fb" in service_uuids
        ):
            self.set_device_type("H5191")
            self.set_device_name(f"H5191 {short_address(address)}")
            (
                temp_probe_1,
                temp_alarm_1,
                _,
                temp,
            ) = PACKED_hhhh.unpack(data[8:16])
            self.update_temp_probe_with_alarm(
                decode_temps_probes(temp_probe_1), decode_temps_probes(temp_alarm_1), 1
            )
            self.update_predefined_sensor(
                SensorLibrary.TEMPERATURE__CELSIUS, temp / 100
            )
            return

        if msg_length == 20 and (
            "H5198" in local_name
            or mgr_id == 0x3022
            or "00009851-0000-1000-8000-00805f9b34fb" in service_uuids
        ):
            sensor_id = data[6]
            self.set_device_type("H5198")
            self.set_device_name(f"H5198 {short_address(address)}")
            (
                temp_probe_first,
                temp_alarm_first,
                low_temp_alarm_first,
                temp_probe_second,
                temp_alarm_second,
                low_temp_alarm_second,
            ) = PACKED_hhhhhh.unpack(data[8:20])
            if not (ids := FOUR_PROBES_MAPPING.get(sensor_id)):
                if debug_logging:
                    _LOGGER.debug(
                        "Unknown sensor id: %s for a H5198, data: %s",
                        sensor_id,
                        hex(data),
                    )
                return
            self.update_temp_probe_with_alarm(
                decode_temps_probes(temp_probe_first),
                decode_temps_probes(temp_alarm_first),
                ids[0],
                decode_temps_probes(low_temp_alarm_first),
            )
            self.update_temp_probe_with_alarm(
                decode_temps_probes(temp_probe_second),
                decode_temps_probes(temp_alarm_second),
                ids[1],
                decode_temps_probes(low_temp_alarm_second),
            )
            return

        if msg_length == 20 and "00005550-0000-1000-8000-00805f9b34fb" in service_uuids:
            self.set_device_type("H5055")
            self.set_device_name(f"H5055 {short_address(address)}")
            (
                temp_probe_first,
                temp_min_first,
                temp_max_first,
                _,
                temp_probe_second,
                temp_min_second,
                temp_max_second,
            ) = PACKED_hhhchhh_LITTLE.unpack(data[5:18])
            sensor_ids = data[3]
            if not (pids := SIX_PROBES_MAPPING.get(sensor_ids & 0xC0)):
                if debug_logging:
                    _LOGGER.debug(
                        "Unknown sensor id: %s for a H5055, data: %s",
                        sensor_ids,
                        hex(data),
                    )
                return
            if sensor_ids & pids[0][1]:
                self.update_temp_probe_with_alarm(
                    temp_probe_first,
                    decode_temps_probes_none(temp_max_first),
                    pids[0][0],
                    decode_temps_probes_none(temp_min_first),
                )
            if sensor_ids & pids[1][1]:
                self.update_temp_probe_with_alarm(
                    temp_probe_second,
                    decode_temps_probes_none(temp_max_second),
                    pids[1][0],
                    decode_temps_probes_none(temp_min_second),
                )
            batt = int(data[2] & 0x7F)
            self.update_predefined_sensor(SensorLibrary.BATTERY__PERCENTAGE, batt)
            return

        if msg_length == 6 and "H5106" in local_name:
            self.set_device_type("H5106")
            self.set_device_name(f"H5106 {short_address(address)}")
            packet_5106 = data[2:6].hex()
            four_bytes = int(packet_5106, 16)
            temp = decode_temps_from_4_bytes(four_bytes)
            humi = decode_humi_from_4_bytes(four_bytes)
            pm25 = decode_pm25_from_4_bytes(four_bytes)
            self.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, temp)
            self.update_predefined_sensor(SensorLibrary.HUMIDITY__PERCENTAGE, humi)
            self.update_predefined_sensor(
                SensorLibrary.PM25__CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, pm25
            )
            return

    def update_temp_probe(self, temp: float | str, probe_id: int) -> None:
        """Update the temperature probe."""
        self.update_predefined_sensor(
            SensorLibrary.TEMPERATURE__CELSIUS,
            temp,
            key=f"temperature_probe_{probe_id}",
            name=f"Temperature Probe {probe_id}",
        )

    def update_humidity_probe(self, humi: float | str, probe_id: int) -> None:
        """Update the humidity probe."""
        self.update_predefined_sensor(
            SensorLibrary.HUMIDITY__PERCENTAGE,
            humi,
            key=f"humidity_probe_{probe_id}",
            name=f"Humidity Probe {probe_id}",
        )

    def update_temp_probe_with_alarm(
        self,
        temp: float,
        alarm_temp: float | None,
        probe_id: int,
        low_alarm_temp: float | None = None,
    ) -> None:
        """Update the temperature probe with the alarm temperature."""
        self.update_temp_probe(temp, probe_id)
        if alarm_temp is not None:
            self.update_predefined_sensor(
                SensorLibrary.TEMPERATURE__CELSIUS,
                alarm_temp,
                key=f"temperature_alarm_probe_{probe_id}",
                name=f"Temperature Alarm Probe {probe_id}",
            )
        if low_alarm_temp is not None:
            self.update_predefined_sensor(
                SensorLibrary.TEMPERATURE__CELSIUS,
                low_alarm_temp,
                key=f"low_temperature_alarm_probe_{probe_id}",
                name=f"Low Temperature Alarm Probe {probe_id}",
            )
