"""Support for Huawei LTE sensors."""

from __future__ import annotations

from bisect import bisect
from collections.abc import Callable, Sequence
from dataclasses import dataclass
from datetime import datetime, timedelta
import logging
import re

from homeassistant.components.sensor import (
    DOMAIN as SENSOR_DOMAIN,
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    PERCENTAGE,
    EntityCategory,
    UnitOfDataRate,
    UnitOfFrequency,
    UnitOfInformation,
    UnitOfTime,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType

from . import HuaweiLteBaseEntityWithDevice, Router
from .const import (
    DOMAIN,
    KEY_DEVICE_INFORMATION,
    KEY_DEVICE_SIGNAL,
    KEY_MONITORING_CHECK_NOTIFICATIONS,
    KEY_MONITORING_MONTH_STATISTICS,
    KEY_MONITORING_STATUS,
    KEY_MONITORING_TRAFFIC_STATISTICS,
    KEY_NET_CURRENT_PLMN,
    KEY_NET_NET_MODE,
    KEY_SMS_SMS_COUNT,
    SENSOR_KEYS,
)

_LOGGER = logging.getLogger(__name__)


def format_default(value: StateType) -> tuple[StateType, str | None]:
    """Format value."""
    unit = None
    if value is not None:
        # Clean up value and infer unit, e.g. -71dBm, 15 dB
        if match := re.match(
            r"((&[gl]t;|[><])=?)?(?P<value>.+?)\s*(?P<unit>[a-zA-Z]+)\s*$", str(value)
        ):
            try:
                value = float(match.group("value"))
                unit = match.group("unit")
            except ValueError:
                pass
    return value, unit


def format_freq_mhz(value: StateType) -> tuple[StateType, UnitOfFrequency]:
    """Format a frequency value for which source is in tenths of MHz."""
    return (
        float(value) / 10 if value is not None else None,
        UnitOfFrequency.MEGAHERTZ,
    )


def format_last_reset_elapsed_seconds(value: str | None) -> datetime | None:
    """Convert elapsed seconds to last reset datetime."""
    if value is None:
        return None
    try:
        last_reset = datetime.now() - timedelta(seconds=int(value))
        last_reset.replace(microsecond=0)
    except ValueError:
        return None
    return last_reset


def signal_icon(limits: Sequence[int], value: StateType) -> str:
    """Get signal icon."""
    return (
        "mdi:signal-cellular-outline",
        "mdi:signal-cellular-1",
        "mdi:signal-cellular-2",
        "mdi:signal-cellular-3",
    )[bisect(limits, value if value is not None else -1000)]


def bandwidth_icon(limits: Sequence[int], value: StateType) -> str:
    """Get bandwidth icon."""
    return (
        "mdi:speedometer-slow",
        "mdi:speedometer-medium",
        "mdi:speedometer",
    )[bisect(limits, value if value is not None else -1000)]


@dataclass
class HuaweiSensorGroup:
    """Class describing Huawei LTE sensor groups."""

    descriptions: dict[str, HuaweiSensorEntityDescription]
    include: re.Pattern[str] | None = None
    exclude: re.Pattern[str] | None = None


@dataclass(frozen=True)
class HuaweiSensorEntityDescription(SensorEntityDescription):
    """Class describing Huawei LTE sensor entities."""

    # HuaweiLteSensor does not support UNDEFINED or None,
    # restrict the type to str.
    name: str = ""

    format_fn: Callable[[str], tuple[StateType, str | None]] = format_default
    icon_fn: Callable[[StateType], str] | None = None
    device_class_fn: Callable[[StateType], SensorDeviceClass | None] | None = None
    last_reset_item: str | None = None
    last_reset_format_fn: Callable[[str | None], datetime | None] | None = None


SENSOR_META: dict[str, HuaweiSensorGroup] = {
    #
    # Device information
    #
    KEY_DEVICE_INFORMATION: HuaweiSensorGroup(
        include=re.compile(r"^(WanIP.*Address|uptime)$", re.IGNORECASE),
        descriptions={
            "uptime": HuaweiSensorEntityDescription(
                key="uptime",
                translation_key="uptime",
                icon="mdi:timer-outline",
                native_unit_of_measurement=UnitOfTime.SECONDS,
                device_class=SensorDeviceClass.DURATION,
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "WanIPAddress": HuaweiSensorEntityDescription(
                key="WanIPAddress",
                translation_key="wan_ip_address",
                icon="mdi:ip",
                entity_category=EntityCategory.DIAGNOSTIC,
                entity_registry_enabled_default=True,
            ),
            "WanIPv6Address": HuaweiSensorEntityDescription(
                key="WanIPv6Address",
                translation_key="wan_ipv6_address",
                icon="mdi:ip",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
        },
    ),
    #
    # Signal
    #
    KEY_DEVICE_SIGNAL: HuaweiSensorGroup(
        descriptions={
            "arfcn": HuaweiSensorEntityDescription(
                key="arfcn",
                translation_key="arfcn",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "band": HuaweiSensorEntityDescription(
                key="band",
                translation_key="band",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "bsic": HuaweiSensorEntityDescription(
                key="bsic",
                translation_key="base_station_identity_code",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "cell_id": HuaweiSensorEntityDescription(
                key="cell_id",
                translation_key="cell_id",
                icon="mdi:transmission-tower",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "cqi0": HuaweiSensorEntityDescription(
                key="cqi0",
                translation_key="cqi0",
                icon="mdi:speedometer",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "cqi1": HuaweiSensorEntityDescription(
                key="cqi1",
                translation_key="cqi1",
                icon="mdi:speedometer",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "dl_mcs": HuaweiSensorEntityDescription(
                key="dl_mcs",
                translation_key="downlink_mcs",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "dlbandwidth": HuaweiSensorEntityDescription(
                key="dlbandwidth",
                translation_key="downlink_bandwidth",
                icon_fn=lambda x: bandwidth_icon((8, 15), x),
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "dlfrequency": HuaweiSensorEntityDescription(
                key="dlfrequency",
                translation_key="downlink_frequency",
                device_class=SensorDeviceClass.FREQUENCY,
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "earfcn": HuaweiSensorEntityDescription(
                key="earfcn",
                translation_key="earfcn",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "ecio": HuaweiSensorEntityDescription(
                key="ecio",
                translation_key="ecio",
                device_class=SensorDeviceClass.SIGNAL_STRENGTH,
                # https://wiki.teltonika.lt/view/EC/IO
                icon_fn=lambda x: signal_icon((-20, -10, -6), x),
                state_class=SensorStateClass.MEASUREMENT,
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "enodeb_id": HuaweiSensorEntityDescription(
                key="enodeb_id",
                translation_key="enodeb_id",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "lac": HuaweiSensorEntityDescription(
                key="lac",
                translation_key="lac",
                icon="mdi:map-marker",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "ltedlfreq": HuaweiSensorEntityDescription(
                key="ltedlfreq",
                translation_key="lte_downlink_frequency",
                format_fn=format_freq_mhz,
                suggested_display_precision=0,
                device_class=SensorDeviceClass.FREQUENCY,
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "lteulfreq": HuaweiSensorEntityDescription(
                key="lteulfreq",
                translation_key="lte_uplink_frequency",
                format_fn=format_freq_mhz,
                suggested_display_precision=0,
                device_class=SensorDeviceClass.FREQUENCY,
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "mode": HuaweiSensorEntityDescription(
                key="mode",
                translation_key="mode",
                format_fn=lambda x: (
                    {"0": "2G", "2": "3G", "7": "4G"}.get(x),
                    None,
                ),
                icon_fn=lambda x: (
                    {
                        "2G": "mdi:signal-2g",
                        "3G": "mdi:signal-3g",
                        "4G": "mdi:signal-4g",
                    }.get(str(x), "mdi:signal")
                ),
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "nrbler": HuaweiSensorEntityDescription(
                key="nrbler",
                translation_key="nrbler",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "nrcqi0": HuaweiSensorEntityDescription(
                key="nrcqi0",
                translation_key="nrcqi0",
                icon="mdi:speedometer",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "nrcqi1": HuaweiSensorEntityDescription(
                key="nrcqi1",
                translation_key="nrcqi1",
                icon="mdi:speedometer",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "nrdlbandwidth": HuaweiSensorEntityDescription(
                key="nrdlbandwidth",
                translation_key="nrdlbandwidth",
                # Could add icon_fn like we have for dlbandwidth,
                # if we find a good source what to use as 5G thresholds.
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "nrdlmcs": HuaweiSensorEntityDescription(
                key="nrdlmcs",
                translation_key="nrdlmcs",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "nrearfcn": HuaweiSensorEntityDescription(
                key="nrearfcn",
                translation_key="nrearfcn",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "nrrank": HuaweiSensorEntityDescription(
                key="nrrank",
                translation_key="nrrank",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "nrrsrp": HuaweiSensorEntityDescription(
                key="nrrsrp",
                translation_key="nrrsrp",
                device_class=SensorDeviceClass.SIGNAL_STRENGTH,
                # Could add icon_fn as in rsrp, source for 5G thresholds?
                state_class=SensorStateClass.MEASUREMENT,
                entity_category=EntityCategory.DIAGNOSTIC,
                entity_registry_enabled_default=True,
            ),
            "nrrsrq": HuaweiSensorEntityDescription(
                key="nrrsrq",
                translation_key="nrrsrq",
                device_class=SensorDeviceClass.SIGNAL_STRENGTH,
                # Could add icon_fn as in rsrq, source for 5G thresholds?
                state_class=SensorStateClass.MEASUREMENT,
                entity_category=EntityCategory.DIAGNOSTIC,
                entity_registry_enabled_default=True,
            ),
            "nrsinr": HuaweiSensorEntityDescription(
                key="nrsinr",
                translation_key="nrsinr",
                device_class=SensorDeviceClass.SIGNAL_STRENGTH,
                # Could add icon_fn as in sinr, source for thresholds?
                state_class=SensorStateClass.MEASUREMENT,
                entity_category=EntityCategory.DIAGNOSTIC,
                entity_registry_enabled_default=True,
            ),
            "nrtxpower": HuaweiSensorEntityDescription(
                key="nrtxpower",
                translation_key="nrtxpower",
                # The value we get from the API tends to consist of several, e.g.
                #     PPusch:21dBm PPucch:2dBm PSrs:0dBm PPrach:10dBm
                # Present as SIGNAL_STRENGTH only if it was parsed to a number.
                # We could try to parse this to separate component sensors sometime.
                device_class_fn=lambda x: (
                    SensorDeviceClass.SIGNAL_STRENGTH
                    if isinstance(x, (float, int))
                    else None
                ),
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "nrulbandwidth": HuaweiSensorEntityDescription(
                key="nrulbandwidth",
                translation_key="nrulbandwidth",
                # Could add icon_fn as in ulbandwidth, source for 5G thresholds?
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "nrulmcs": HuaweiSensorEntityDescription(
                key="nrulmcs",
                translation_key="nrulmcs",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "pci": HuaweiSensorEntityDescription(
                key="pci",
                translation_key="pci",
                icon="mdi:transmission-tower",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "plmn": HuaweiSensorEntityDescription(
                key="plmn",
                translation_key="plmn",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "rac": HuaweiSensorEntityDescription(
                key="rac",
                translation_key="rac",
                icon="mdi:map-marker",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "rrc_status": HuaweiSensorEntityDescription(
                key="rrc_status",
                translation_key="rrc_status",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "rscp": HuaweiSensorEntityDescription(
                key="rscp",
                translation_key="rscp",
                device_class=SensorDeviceClass.SIGNAL_STRENGTH,
                # https://wiki.teltonika.lt/view/RSCP
                icon_fn=lambda x: signal_icon((-95, -85, -75), x),
                state_class=SensorStateClass.MEASUREMENT,
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "rsrp": HuaweiSensorEntityDescription(
                key="rsrp",
                translation_key="rsrp",
                device_class=SensorDeviceClass.SIGNAL_STRENGTH,
                # http://www.lte-anbieter.info/technik/rsrp.php  # codespell:ignore technik
                icon_fn=lambda x: signal_icon((-110, -95, -80), x),
                state_class=SensorStateClass.MEASUREMENT,
                entity_category=EntityCategory.DIAGNOSTIC,
                entity_registry_enabled_default=True,
            ),
            "rsrq": HuaweiSensorEntityDescription(
                key="rsrq",
                translation_key="rsrq",
                device_class=SensorDeviceClass.SIGNAL_STRENGTH,
                # http://www.lte-anbieter.info/technik/rsrq.php  # codespell:ignore technik
                icon_fn=lambda x: signal_icon((-11, -8, -5), x),
                state_class=SensorStateClass.MEASUREMENT,
                entity_category=EntityCategory.DIAGNOSTIC,
                entity_registry_enabled_default=True,
            ),
            "rssi": HuaweiSensorEntityDescription(
                key="rssi",
                translation_key="rssi",
                device_class=SensorDeviceClass.SIGNAL_STRENGTH,
                # https://eyesaas.com/wi-fi-signal-strength/
                icon_fn=lambda x: signal_icon((-80, -70, -60), x),
                state_class=SensorStateClass.MEASUREMENT,
                entity_category=EntityCategory.DIAGNOSTIC,
                entity_registry_enabled_default=True,
            ),
            "sinr": HuaweiSensorEntityDescription(
                key="sinr",
                translation_key="sinr",
                device_class=SensorDeviceClass.SIGNAL_STRENGTH,
                # http://www.lte-anbieter.info/technik/sinr.php  # codespell:ignore technik
                icon_fn=lambda x: signal_icon((0, 5, 10), x),
                state_class=SensorStateClass.MEASUREMENT,
                entity_category=EntityCategory.DIAGNOSTIC,
                entity_registry_enabled_default=True,
            ),
            "tac": HuaweiSensorEntityDescription(
                key="tac",
                translation_key="tac",
                icon="mdi:map-marker",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "tdd": HuaweiSensorEntityDescription(
                key="tdd",
                translation_key="tdd",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "transmode": HuaweiSensorEntityDescription(
                key="transmode",
                translation_key="transmission_mode",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "txpower": HuaweiSensorEntityDescription(
                key="txpower",
                translation_key="transmit_power",
                # The value we get from the API tends to consist of several, e.g.
                #     PPusch:15dBm PPucch:2dBm PSrs:42dBm PPrach:1dBm
                # Present as SIGNAL_STRENGTH only if it was parsed to a number.
                # We could try to parse this to separate component sensors sometime.
                device_class_fn=lambda x: (
                    SensorDeviceClass.SIGNAL_STRENGTH
                    if isinstance(x, (float, int))
                    else None
                ),
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "ul_mcs": HuaweiSensorEntityDescription(
                key="ul_mcs",
                translation_key="uplink_mcs",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "ulbandwidth": HuaweiSensorEntityDescription(
                key="ulbandwidth",
                translation_key="uplink_bandwidth",
                icon_fn=lambda x: bandwidth_icon((8, 15), x),
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "ulfrequency": HuaweiSensorEntityDescription(
                key="ulfrequency",
                translation_key="uplink_frequency",
                device_class=SensorDeviceClass.FREQUENCY,
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
        }
    ),
    #
    # Monitoring
    #
    KEY_MONITORING_CHECK_NOTIFICATIONS: HuaweiSensorGroup(
        exclude=re.compile(
            r"^(onlineupdatestatus|smsstoragefull)$",
            re.IGNORECASE,
        ),
        descriptions={
            "UnreadMessage": HuaweiSensorEntityDescription(
                key="UnreadMessage",
                translation_key="sms_unread",
                icon="mdi:email-arrow-left",
            ),
        },
    ),
    KEY_MONITORING_MONTH_STATISTICS: HuaweiSensorGroup(
        exclude=re.compile(
            r"^(currentday|month)(duration|lastcleartime)$", re.IGNORECASE
        ),
        descriptions={
            "CurrentDayUsed": HuaweiSensorEntityDescription(
                key="CurrentDayUsed",
                translation_key="current_day_transfer",
                native_unit_of_measurement=UnitOfInformation.BYTES,
                device_class=SensorDeviceClass.DATA_SIZE,
                icon="mdi:arrow-up-down-bold",
                state_class=SensorStateClass.TOTAL,
                last_reset_item="CurrentDayDuration",
                last_reset_format_fn=format_last_reset_elapsed_seconds,
            ),
            "CurrentMonthDownload": HuaweiSensorEntityDescription(
                key="CurrentMonthDownload",
                translation_key="current_month_download",
                native_unit_of_measurement=UnitOfInformation.BYTES,
                device_class=SensorDeviceClass.DATA_SIZE,
                icon="mdi:download",
                state_class=SensorStateClass.TOTAL,
                last_reset_item="MonthDuration",
                last_reset_format_fn=format_last_reset_elapsed_seconds,
            ),
            "CurrentMonthUpload": HuaweiSensorEntityDescription(
                key="CurrentMonthUpload",
                translation_key="current_month_upload",
                native_unit_of_measurement=UnitOfInformation.BYTES,
                device_class=SensorDeviceClass.DATA_SIZE,
                icon="mdi:upload",
                state_class=SensorStateClass.TOTAL,
                last_reset_item="MonthDuration",
                last_reset_format_fn=format_last_reset_elapsed_seconds,
            ),
        },
    ),
    KEY_MONITORING_STATUS: HuaweiSensorGroup(
        include=re.compile(
            r"^(batterypercent|currentwifiuser|(primary|secondary).*dns)$",
            re.IGNORECASE,
        ),
        descriptions={
            "BatteryPercent": HuaweiSensorEntityDescription(
                key="BatteryPercent",
                device_class=SensorDeviceClass.BATTERY,
                native_unit_of_measurement=PERCENTAGE,
                state_class=SensorStateClass.MEASUREMENT,
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "CurrentWifiUser": HuaweiSensorEntityDescription(
                key="CurrentWifiUser",
                translation_key="wifi_clients_connected",
                icon="mdi:wifi",
                state_class=SensorStateClass.MEASUREMENT,
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "PrimaryDns": HuaweiSensorEntityDescription(
                key="PrimaryDns",
                translation_key="primary_dns_server",
                icon="mdi:ip",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "PrimaryIPv6Dns": HuaweiSensorEntityDescription(
                key="PrimaryIPv6Dns",
                translation_key="primary_ipv6_dns_server",
                icon="mdi:ip",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "SecondaryDns": HuaweiSensorEntityDescription(
                key="SecondaryDns",
                translation_key="secondary_dns_server",
                icon="mdi:ip",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "SecondaryIPv6Dns": HuaweiSensorEntityDescription(
                key="SecondaryIPv6Dns",
                translation_key="secondary_ipv6_dns_server",
                icon="mdi:ip",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
        },
    ),
    KEY_MONITORING_TRAFFIC_STATISTICS: HuaweiSensorGroup(
        exclude=re.compile(r"^showtraffic$", re.IGNORECASE),
        descriptions={
            "CurrentConnectTime": HuaweiSensorEntityDescription(
                key="CurrentConnectTime",
                translation_key="current_connection_duration",
                native_unit_of_measurement=UnitOfTime.SECONDS,
                device_class=SensorDeviceClass.DURATION,
                icon="mdi:timer-outline",
            ),
            "CurrentDownload": HuaweiSensorEntityDescription(
                key="CurrentDownload",
                translation_key="current_connection_download",
                native_unit_of_measurement=UnitOfInformation.BYTES,
                device_class=SensorDeviceClass.DATA_SIZE,
                icon="mdi:download",
                state_class=SensorStateClass.TOTAL_INCREASING,
            ),
            "CurrentDownloadRate": HuaweiSensorEntityDescription(
                key="CurrentDownloadRate",
                translation_key="current_download_rate",
                native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
                device_class=SensorDeviceClass.DATA_RATE,
                icon="mdi:download",
                state_class=SensorStateClass.MEASUREMENT,
            ),
            "CurrentUpload": HuaweiSensorEntityDescription(
                key="CurrentUpload",
                translation_key="current_connection_upload",
                native_unit_of_measurement=UnitOfInformation.BYTES,
                device_class=SensorDeviceClass.DATA_SIZE,
                icon="mdi:upload",
                state_class=SensorStateClass.TOTAL_INCREASING,
            ),
            "CurrentUploadRate": HuaweiSensorEntityDescription(
                key="CurrentUploadRate",
                translation_key="current_upload_rate",
                native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
                device_class=SensorDeviceClass.DATA_RATE,
                icon="mdi:upload",
                state_class=SensorStateClass.MEASUREMENT,
            ),
            "TotalConnectTime": HuaweiSensorEntityDescription(
                key="TotalConnectTime",
                translation_key="total_connected_duration",
                native_unit_of_measurement=UnitOfTime.SECONDS,
                device_class=SensorDeviceClass.DURATION,
                icon="mdi:timer-outline",
                state_class=SensorStateClass.TOTAL_INCREASING,
            ),
            "TotalDownload": HuaweiSensorEntityDescription(
                key="TotalDownload",
                translation_key="total_download",
                native_unit_of_measurement=UnitOfInformation.BYTES,
                device_class=SensorDeviceClass.DATA_SIZE,
                icon="mdi:download",
                state_class=SensorStateClass.TOTAL_INCREASING,
            ),
            "TotalUpload": HuaweiSensorEntityDescription(
                key="TotalUpload",
                translation_key="total_upload",
                native_unit_of_measurement=UnitOfInformation.BYTES,
                device_class=SensorDeviceClass.DATA_SIZE,
                icon="mdi:upload",
                state_class=SensorStateClass.TOTAL_INCREASING,
            ),
        },
    ),
    #
    # Network
    #
    KEY_NET_CURRENT_PLMN: HuaweiSensorGroup(
        exclude=re.compile(r"^(Rat|ShortName|Spn)$", re.IGNORECASE),
        descriptions={
            "FullName": HuaweiSensorEntityDescription(
                key="FullName",
                translation_key="operator_name",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "Numeric": HuaweiSensorEntityDescription(
                key="Numeric",
                translation_key="operator_code",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
            "State": HuaweiSensorEntityDescription(
                key="State",
                translation_key="operator_search_mode",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
        },
    ),
    KEY_NET_NET_MODE: HuaweiSensorGroup(
        include=re.compile(r"^NetworkMode$", re.IGNORECASE),
        descriptions={
            "NetworkMode": HuaweiSensorEntityDescription(
                key="NetworkMode",
                translation_key="preferred_network_mode",
                entity_category=EntityCategory.DIAGNOSTIC,
            ),
        },
    ),
    #
    # SMS
    #
    KEY_SMS_SMS_COUNT: HuaweiSensorGroup(
        descriptions={
            "LocalDeleted": HuaweiSensorEntityDescription(
                key="LocalDeleted",
                translation_key="sms_deleted_device",
                icon="mdi:email-minus",
            ),
            "LocalDraft": HuaweiSensorEntityDescription(
                key="LocalDraft",
                translation_key="sms_drafts_device",
                icon="mdi:email-arrow-right-outline",
            ),
            "LocalInbox": HuaweiSensorEntityDescription(
                key="LocalInbox",
                translation_key="sms_inbox_device",
                icon="mdi:email",
            ),
            "LocalMax": HuaweiSensorEntityDescription(
                key="LocalMax",
                translation_key="sms_capacity_device",
                icon="mdi:email",
            ),
            "LocalOutbox": HuaweiSensorEntityDescription(
                key="LocalOutbox",
                translation_key="sms_outbox_device",
                icon="mdi:email-arrow-right",
            ),
            "LocalUnread": HuaweiSensorEntityDescription(
                key="LocalUnread",
                translation_key="sms_unread_device",
                icon="mdi:email-arrow-left",
            ),
            "SimDraft": HuaweiSensorEntityDescription(
                key="SimDraft",
                translation_key="sms_drafts_sim",
                icon="mdi:email-arrow-right-outline",
            ),
            "SimInbox": HuaweiSensorEntityDescription(
                key="SimInbox",
                translation_key="sms_inbox_sim",
                icon="mdi:email",
            ),
            "SimMax": HuaweiSensorEntityDescription(
                key="SimMax",
                translation_key="sms_capacity_sim",
                icon="mdi:email",
            ),
            "SimOutbox": HuaweiSensorEntityDescription(
                key="SimOutbox",
                translation_key="sms_outbox_sim",
                icon="mdi:email-arrow-right",
            ),
            "SimUnread": HuaweiSensorEntityDescription(
                key="SimUnread",
                translation_key="sms_unread_sim",
                icon="mdi:email-arrow-left",
            ),
            "SimUsed": HuaweiSensorEntityDescription(
                key="SimUsed",
                translation_key="sms_messages_sim",
                icon="mdi:email-arrow-left",
            ),
        },
    ),
}


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up from config entry."""
    router = hass.data[DOMAIN].routers[config_entry.entry_id]
    sensors: list[Entity] = []
    for key in SENSOR_KEYS:
        if not (items := router.data.get(key)):
            continue
        if key_meta := SENSOR_META.get(key):
            if key_meta.include:
                items = filter(key_meta.include.search, items)
            if key_meta.exclude:
                items = [x for x in items if not key_meta.exclude.search(x)]
        sensors.extend(
            HuaweiLteSensor(
                router,
                key,
                item,
                SENSOR_META[key].descriptions.get(
                    item, HuaweiSensorEntityDescription(key=item)
                ),
            )
            for item in items
        )

    async_add_entities(sensors, True)


class HuaweiLteSensor(HuaweiLteBaseEntityWithDevice, SensorEntity):
    """Huawei LTE sensor entity."""

    entity_description: HuaweiSensorEntityDescription
    _state: StateType = None
    _unit: str | None = None
    _last_reset: datetime | None = None

    def __init__(
        self,
        router: Router,
        key: str,
        item: str,
        entity_description: HuaweiSensorEntityDescription,
    ) -> None:
        """Initialize."""
        super().__init__(router)
        self.key = key
        self.item = item
        self.entity_description = entity_description

    async def async_added_to_hass(self) -> None:
        """Subscribe to needed data on add."""
        await super().async_added_to_hass()
        self.router.subscriptions[self.key].append(f"{SENSOR_DOMAIN}/{self.item}")
        if self.entity_description.last_reset_item:
            self.router.subscriptions[self.key].append(
                f"{SENSOR_DOMAIN}/{self.entity_description.last_reset_item}"
            )

    async def async_will_remove_from_hass(self) -> None:
        """Unsubscribe from needed data on remove."""
        await super().async_will_remove_from_hass()
        self.router.subscriptions[self.key].remove(f"{SENSOR_DOMAIN}/{self.item}")
        if self.entity_description.last_reset_item:
            self.router.subscriptions[self.key].remove(
                f"{SENSOR_DOMAIN}/{self.entity_description.last_reset_item}"
            )

    @property
    def _device_unique_id(self) -> str:
        return f"{self.key}.{self.item}"

    @property
    def native_value(self) -> StateType:
        """Return sensor state."""
        return self._state

    @property
    def native_unit_of_measurement(self) -> str | None:
        """Return sensor's unit of measurement."""
        return self.entity_description.native_unit_of_measurement or self._unit

    @property
    def icon(self) -> str | None:
        """Return icon for sensor."""
        if self.entity_description.icon_fn:
            return self.entity_description.icon_fn(self.state)
        return self.entity_description.icon

    @property
    def device_class(self) -> SensorDeviceClass | None:
        """Return device class for sensor."""
        if self.entity_description.device_class_fn:
            # Note: using self.state could infloop here.
            return self.entity_description.device_class_fn(self.native_value)
        return super().device_class

    @property
    def last_reset(self) -> datetime | None:
        """Return the time when the sensor was last reset, if any."""
        return self._last_reset

    async def async_update(self) -> None:
        """Update state."""
        try:
            value = self.router.data[self.key][self.item]
        except KeyError:
            _LOGGER.debug("%s[%s] not in data", self.key, self.item)
            value = None

        last_reset = None
        if (
            self.entity_description.last_reset_item
            and self.entity_description.last_reset_format_fn
        ):
            try:
                last_reset_value = self.router.data[self.key][
                    self.entity_description.last_reset_item
                ]
            except KeyError:
                _LOGGER.debug(
                    "%s[%s] not in data",
                    self.key,
                    self.entity_description.last_reset_item,
                )
            else:
                last_reset = self.entity_description.last_reset_format_fn(
                    last_reset_value
                )

        self._state, self._unit = self.entity_description.format_fn(value)
        self._last_reset = last_reset
        self._available = value is not None
