"""Support for APCUPSd sensors."""

from __future__ import annotations

import logging

import dateutil

from homeassistant.components.automation import automations_with_entity
from homeassistant.components.script import scripts_with_entity
from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.const import (
    PERCENTAGE,
    EntityCategory,
    UnitOfApparentPower,
    UnitOfElectricCurrent,
    UnitOfElectricPotential,
    UnitOfFrequency,
    UnitOfPower,
    UnitOfTemperature,
    UnitOfTime,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
import homeassistant.helpers.issue_registry as ir

from .const import AVAILABLE_VIA_DEVICE_ATTR, DEPRECATED_SENSORS, DOMAIN, LAST_S_TEST
from .coordinator import APCUPSdConfigEntry, APCUPSdCoordinator
from .entity import APCUPSdEntity

PARALLEL_UPDATES = 0

_LOGGER = logging.getLogger(__name__)

SENSORS: dict[str, SensorEntityDescription] = {
    "alarmdel": SensorEntityDescription(
        key="alarmdel",
        translation_key="alarm_delay",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "ambtemp": SensorEntityDescription(
        key="ambtemp",
        translation_key="ambient_temperature",
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    "apc": SensorEntityDescription(
        key="apc",
        translation_key="apc_status",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "apcmodel": SensorEntityDescription(
        key="apcmodel",
        translation_key="apc_model",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "badbatts": SensorEntityDescription(
        key="badbatts",
        translation_key="bad_batteries",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "battdate": SensorEntityDescription(
        key="battdate",
        translation_key="battery_replacement_date",
    ),
    "battstat": SensorEntityDescription(
        key="battstat",
        translation_key="battery_status",
    ),
    "battv": SensorEntityDescription(
        key="battv",
        translation_key="battery_voltage",
        native_unit_of_measurement=UnitOfElectricPotential.VOLT,
        device_class=SensorDeviceClass.VOLTAGE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    "bcharge": SensorEntityDescription(
        key="bcharge",
        native_unit_of_measurement=PERCENTAGE,
        device_class=SensorDeviceClass.BATTERY,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    "cable": SensorEntityDescription(
        key="cable",
        translation_key="cable_type",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "cumonbatt": SensorEntityDescription(
        key="cumonbatt",
        translation_key="total_time_on_battery",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        state_class=SensorStateClass.TOTAL_INCREASING,
        device_class=SensorDeviceClass.DURATION,
    ),
    "date": SensorEntityDescription(
        key="date",
        translation_key="date",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "dipsw": SensorEntityDescription(
        key="dipsw",
        translation_key="dip_switch_settings",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "dlowbatt": SensorEntityDescription(
        key="dlowbatt",
        translation_key="low_battery_signal",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "driver": SensorEntityDescription(
        key="driver",
        translation_key="driver",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "dshutd": SensorEntityDescription(
        key="dshutd",
        translation_key="shutdown_delay",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "dwake": SensorEntityDescription(
        key="dwake",
        translation_key="wake_delay",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "end apc": SensorEntityDescription(
        key="end apc",
        translation_key="date_and_time",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "extbatts": SensorEntityDescription(
        key="extbatts",
        translation_key="external_batteries",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "firmware": SensorEntityDescription(
        key="firmware",
        translation_key="firmware_version",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "hitrans": SensorEntityDescription(
        key="hitrans",
        translation_key="transfer_high",
        native_unit_of_measurement=UnitOfElectricPotential.VOLT,
        device_class=SensorDeviceClass.VOLTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "hostname": SensorEntityDescription(
        key="hostname",
        translation_key="hostname",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "humidity": SensorEntityDescription(
        key="humidity",
        translation_key="humidity",
        native_unit_of_measurement=PERCENTAGE,
        device_class=SensorDeviceClass.HUMIDITY,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    "itemp": SensorEntityDescription(
        key="itemp",
        translation_key="internal_temperature",
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    LAST_S_TEST: SensorEntityDescription(
        key=LAST_S_TEST,
        translation_key="last_self_test",
        device_class=SensorDeviceClass.TIMESTAMP,
    ),
    "lastxfer": SensorEntityDescription(
        key="lastxfer",
        translation_key="last_transfer",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "linefail": SensorEntityDescription(
        key="linefail",
        translation_key="line_failure",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "linefreq": SensorEntityDescription(
        key="linefreq",
        translation_key="line_frequency",
        native_unit_of_measurement=UnitOfFrequency.HERTZ,
        device_class=SensorDeviceClass.FREQUENCY,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    "linev": SensorEntityDescription(
        key="linev",
        translation_key="line_voltage",
        native_unit_of_measurement=UnitOfElectricPotential.VOLT,
        device_class=SensorDeviceClass.VOLTAGE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    "loadpct": SensorEntityDescription(
        key="loadpct",
        translation_key="load_capacity",
        native_unit_of_measurement=PERCENTAGE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    "loadapnt": SensorEntityDescription(
        key="loadapnt",
        translation_key="apparent_power",
        native_unit_of_measurement=PERCENTAGE,
    ),
    "lotrans": SensorEntityDescription(
        key="lotrans",
        translation_key="transfer_low",
        native_unit_of_measurement=UnitOfElectricPotential.VOLT,
        device_class=SensorDeviceClass.VOLTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "mandate": SensorEntityDescription(
        key="mandate",
        translation_key="manufacture_date",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "masterupd": SensorEntityDescription(
        key="masterupd",
        translation_key="master_update",
        device_class=SensorDeviceClass.TIMESTAMP,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "maxlinev": SensorEntityDescription(
        key="maxlinev",
        translation_key="input_voltage_high",
        native_unit_of_measurement=UnitOfElectricPotential.VOLT,
        device_class=SensorDeviceClass.VOLTAGE,
    ),
    "maxtime": SensorEntityDescription(
        key="maxtime",
        translation_key="max_time",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "mbattchg": SensorEntityDescription(
        key="mbattchg",
        translation_key="max_battery_charge",
        native_unit_of_measurement=PERCENTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "minlinev": SensorEntityDescription(
        key="minlinev",
        translation_key="input_voltage_low",
        native_unit_of_measurement=UnitOfElectricPotential.VOLT,
        device_class=SensorDeviceClass.VOLTAGE,
    ),
    "mintimel": SensorEntityDescription(
        key="mintimel",
        translation_key="min_time",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "model": SensorEntityDescription(
        key="model",
        translation_key="model",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "nombattv": SensorEntityDescription(
        key="nombattv",
        translation_key="battery_nominal_voltage",
        native_unit_of_measurement=UnitOfElectricPotential.VOLT,
        device_class=SensorDeviceClass.VOLTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "nominv": SensorEntityDescription(
        key="nominv",
        translation_key="nominal_input_voltage",
        native_unit_of_measurement=UnitOfElectricPotential.VOLT,
        device_class=SensorDeviceClass.VOLTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "nomoutv": SensorEntityDescription(
        key="nomoutv",
        translation_key="nominal_output_voltage",
        native_unit_of_measurement=UnitOfElectricPotential.VOLT,
        device_class=SensorDeviceClass.VOLTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "nompower": SensorEntityDescription(
        key="nompower",
        translation_key="nominal_output_power",
        native_unit_of_measurement=UnitOfPower.WATT,
        device_class=SensorDeviceClass.POWER,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "nomapnt": SensorEntityDescription(
        key="nomapnt",
        translation_key="nominal_apparent_power",
        native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
        device_class=SensorDeviceClass.APPARENT_POWER,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "numxfers": SensorEntityDescription(
        key="numxfers",
        translation_key="transfer_count",
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    "outcurnt": SensorEntityDescription(
        key="outcurnt",
        translation_key="output_current",
        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
        device_class=SensorDeviceClass.CURRENT,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    "outputv": SensorEntityDescription(
        key="outputv",
        translation_key="output_voltage",
        native_unit_of_measurement=UnitOfElectricPotential.VOLT,
        device_class=SensorDeviceClass.VOLTAGE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    "reg1": SensorEntityDescription(
        key="reg1",
        translation_key="register_1_fault",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "reg2": SensorEntityDescription(
        key="reg2",
        translation_key="register_2_fault",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "reg3": SensorEntityDescription(
        key="reg3",
        translation_key="register_3_fault",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "retpct": SensorEntityDescription(
        key="retpct",
        translation_key="restore_capacity",
        native_unit_of_measurement=PERCENTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "selftest": SensorEntityDescription(
        key="selftest",
        translation_key="self_test_result",
    ),
    "sense": SensorEntityDescription(
        key="sense",
        translation_key="sensitivity",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "serialno": SensorEntityDescription(
        key="serialno",
        translation_key="serial_number",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "starttime": SensorEntityDescription(
        key="starttime",
        translation_key="startup_time",
        device_class=SensorDeviceClass.TIMESTAMP,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "statflag": SensorEntityDescription(
        key="statflag",
        translation_key="online_status",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "status": SensorEntityDescription(
        key="status",
        translation_key="status",
    ),
    "stesti": SensorEntityDescription(
        key="stesti",
        translation_key="self_test_interval",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "timeleft": SensorEntityDescription(
        key="timeleft",
        translation_key="time_left",
        native_unit_of_measurement=UnitOfTime.MINUTES,
        state_class=SensorStateClass.MEASUREMENT,
        device_class=SensorDeviceClass.DURATION,
    ),
    "tonbatt": SensorEntityDescription(
        key="tonbatt",
        translation_key="time_on_battery",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        state_class=SensorStateClass.TOTAL_INCREASING,
        device_class=SensorDeviceClass.DURATION,
    ),
    "upsmode": SensorEntityDescription(
        key="upsmode",
        translation_key="ups_mode",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "upsname": SensorEntityDescription(
        key="upsname",
        translation_key="ups_name",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "version": SensorEntityDescription(
        key="version",
        translation_key="version",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "xoffbat": SensorEntityDescription(
        key="xoffbat",
        translation_key="transfer_from_battery",
        device_class=SensorDeviceClass.TIMESTAMP,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "xoffbatt": SensorEntityDescription(
        key="xoffbatt",
        translation_key="transfer_from_battery",
        device_class=SensorDeviceClass.TIMESTAMP,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "xonbatt": SensorEntityDescription(
        key="xonbatt",
        translation_key="transfer_to_battery",
        device_class=SensorDeviceClass.TIMESTAMP,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
}

INFERRED_UNITS = {
    " Minutes": UnitOfTime.MINUTES,
    " Seconds": UnitOfTime.SECONDS,
    " Percent": PERCENTAGE,
    " Volts": UnitOfElectricPotential.VOLT,
    " Ampere": UnitOfElectricCurrent.AMPERE,
    " Amps": UnitOfElectricCurrent.AMPERE,
    " Volt-Ampere": UnitOfApparentPower.VOLT_AMPERE,
    " VA": UnitOfApparentPower.VOLT_AMPERE,
    " Watts": UnitOfPower.WATT,
    " Hz": UnitOfFrequency.HERTZ,
    " C": UnitOfTemperature.CELSIUS,
    # APCUPSd reports data for "itemp" field (eventually represented by UPS Internal
    # Temperature sensor in this integration) with a trailing "Internal", e.g.,
    # "34.6 C Internal". Here we create a fake unit " C Internal" to handle this case.
    " C Internal": UnitOfTemperature.CELSIUS,
    " Percent Load Capacity": PERCENTAGE,
    # "stesti" field (Self Test Interval) field could report a "days" unit, e.g.,
    # "7 days", so here we add support for it.
    " days": UnitOfTime.DAYS,
}


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: APCUPSdConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up the APCUPSd sensors from config entries."""
    coordinator = config_entry.runtime_data

    # The resource keys in the data dict collected in the coordinator is in upper-case
    # by default, but we use lower cases throughout this integration.
    available_resources: set[str] = {k.lower() for k, _ in coordinator.data.items()}

    entities = []

    # "laststest" is a special sensor that only appears when the APC UPS daemon has done a
    # periodical (or manual) self test since last daemon restart. It might not be available
    # when we set up the integration, and we do not know if it would ever be available. Here we
    # add it anyway and mark it as unknown initially.
    #
    # We also sort the resources to ensure the order of entities created is deterministic since
    # "APCMODEL" and "MODEL" resources map to the same "Model" name.
    for resource in sorted(available_resources | {LAST_S_TEST}):
        if resource not in SENSORS:
            _LOGGER.warning("Invalid resource from APCUPSd: %s", resource.upper())
            continue

        entities.append(APCUPSdSensor(coordinator, SENSORS[resource]))

    async_add_entities(entities)


def infer_unit(value: str) -> tuple[str, str | None]:
    """If the value ends with any of the units from supported units.

    Split the unit off the end of the value and return the value, unit tuple
    pair. Else return the original value and None as the unit.
    """

    for unit, ha_unit in INFERRED_UNITS.items():
        if value.endswith(unit):
            return value.removesuffix(unit), ha_unit

    return value, None


class APCUPSdSensor(APCUPSdEntity, SensorEntity):
    """Representation of a sensor entity for APCUPSd status values."""

    def __init__(
        self,
        coordinator: APCUPSdCoordinator,
        description: SensorEntityDescription,
    ) -> None:
        """Initialize the sensor."""
        super().__init__(coordinator, description)

        # Initial update of attributes.
        self._update_attrs()

    @callback
    def _handle_coordinator_update(self) -> None:
        """Handle updated data from the coordinator."""
        self._update_attrs()
        self.async_write_ha_state()

    def _update_attrs(self) -> None:
        """Update sensor attributes based on coordinator data."""
        key = self.entity_description.key.upper()
        # For most sensors the key will always be available for each refresh. However, some sensors
        # (e.g., "laststest") will only appear after certain event occurs (e.g., a self test is
        # performed) and may disappear again after certain event. So we mark the state as "unknown"
        # when it becomes unknown after such events.
        if key not in self.coordinator.data:
            self._attr_native_value = None
            return

        data = self.coordinator.data[key]

        if self.entity_description.device_class == SensorDeviceClass.TIMESTAMP:
            # The date could be "N/A" for certain fields (e.g., XOFFBATT), indicating there is no value yet.
            if data == "N/A":
                self._attr_native_value = None
                return

            try:
                self._attr_native_value = dateutil.parser.parse(data)
            except dateutil.parser.ParserError, OverflowError:
                # If parsing fails we should mark it as unknown, with a log for further debugging.
                _LOGGER.warning('Failed to parse date for %s: "%s"', key, data)
                self._attr_native_value = None
            return

        self._attr_native_value, inferred_unit = infer_unit(data)
        if not self.native_unit_of_measurement:
            self._attr_native_unit_of_measurement = inferred_unit

    async def async_added_to_hass(self) -> None:
        """Handle when entity is added to Home Assistant.

        If this is a deprecated sensor entity, create a repair issue to guide
        the user to disable it.
        """
        await super().async_added_to_hass()

        if not self.enabled:
            return

        reason = DEPRECATED_SENSORS.get(self.entity_description.key)
        if not reason:
            return

        automations = automations_with_entity(self.hass, self.entity_id)
        scripts = scripts_with_entity(self.hass, self.entity_id)
        if not automations and not scripts:
            return

        entity_registry = er.async_get(self.hass)
        items = [
            f"- [{entry.name or entry.original_name or entity_id}]"
            f"(/config/{integration}/edit/{entry.unique_id or entity_id.split('.', 1)[-1]})"
            for integration, entities in (
                ("automation", automations),
                ("script", scripts),
            )
            for entity_id in entities
            if (entry := entity_registry.async_get(entity_id))
        ]
        placeholders = {
            "entity_name": str(self.name or self.entity_id),
            "entity_id": self.entity_id,
            "items": "\n".join(items),
        }
        if via_attr := AVAILABLE_VIA_DEVICE_ATTR.get(self.entity_description.key):
            placeholders["available_via_device_attr"] = via_attr
        if device_entry := self.device_entry:
            placeholders["device_id"] = device_entry.id

        ir.async_create_issue(
            self.hass,
            DOMAIN,
            f"{reason}_{self.entity_id}",
            breaks_in_ha_version="2026.6.0",
            is_fixable=False,
            severity=ir.IssueSeverity.WARNING,
            translation_key=reason,
            translation_placeholders=placeholders,
        )

    async def async_will_remove_from_hass(self) -> None:
        """Handle when entity will be removed from Home Assistant."""
        await super().async_will_remove_from_hass()

        if issue_key := DEPRECATED_SENSORS.get(self.entity_description.key):
            ir.async_delete_issue(self.hass, DOMAIN, f"{issue_key}_{self.entity_id}")
