"""Support for gauges from flood monitoring API."""

from typing import Any

from homeassistant.components.sensor import SensorEntity, SensorStateClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfLength
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import (
    CoordinatorEntity,
    DataUpdateCoordinator,
)

from .const import DOMAIN

UNIT_MAPPING = {
    "http://qudt.org/1.1/vocab/unit#Meter": UnitOfLength.METERS,
}


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up UK Flood Monitoring Sensors."""
    coordinator: DataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
    created_entities: set[str] = set()

    @callback
    def _async_create_new_entities():
        """Create new entities."""
        if not coordinator.last_update_success:
            return
        measures: dict[str, dict[str, Any]] = coordinator.data["measures"]
        entities: list[Measurement] = []
        # Look to see if payload contains new measures
        for key, data in measures.items():
            if key in created_entities:
                continue

            if "latestReading" not in data:
                # Don't create a sensor entity for a gauge that isn't available
                continue

            entities.append(Measurement(coordinator, key))
            created_entities.add(key)

        async_add_entities(entities)

    _async_create_new_entities()

    # Subscribe to the coordinator to create new entities
    # when the coordinator updates
    config_entry.async_on_unload(
        coordinator.async_add_listener(_async_create_new_entities)
    )


class Measurement(CoordinatorEntity, SensorEntity):
    """A gauge at a flood monitoring station."""

    _attr_attribution = (
        "This uses Environment Agency flood and river level data "
        "from the real-time data API"
    )
    _attr_state_class = SensorStateClass.MEASUREMENT
    _attr_has_entity_name = True
    _attr_name = None

    def __init__(self, coordinator, key):
        """Initialise the gauge with a data instance and station."""
        super().__init__(coordinator)
        self.key = key
        self._attr_unique_id = key

    @property
    def station_name(self):
        """Return the station name for the measure."""
        return self.coordinator.data["label"]

    @property
    def station_id(self):
        """Return the station id for the measure."""
        return self.coordinator.data["measures"][self.key]["stationReference"]

    @property
    def qualifier(self):
        """Return the qualifier for the station."""
        return self.coordinator.data["measures"][self.key]["qualifier"]

    @property
    def parameter_name(self):
        """Return the parameter name for the station."""
        return self.coordinator.data["measures"][self.key]["parameterName"]

    @property
    def device_info(self):
        """Return the device info."""
        return DeviceInfo(
            entry_type=DeviceEntryType.SERVICE,
            identifiers={(DOMAIN, "measure-id", self.station_id)},
            manufacturer="https://environment.data.gov.uk/",
            model=self.parameter_name,
            name=f"{self.station_name} {self.parameter_name} {self.qualifier}",
        )

    @property
    def available(self) -> bool:
        """Return True if entity is available."""
        if not self.coordinator.last_update_success:
            return False

        # If sensor goes offline it will no longer contain a reading
        if "latestReading" not in self.coordinator.data["measures"][self.key]:
            return False

        # Sometimes lastestReading key is present but actually a URL rather than a piece of data
        # This is usually because the sensor has been archived
        if not isinstance(
            self.coordinator.data["measures"][self.key]["latestReading"], dict
        ):
            return False

        return True

    @property
    def native_unit_of_measurement(self):
        """Return units for the sensor."""
        measure = self.coordinator.data["measures"][self.key]
        if "unit" not in measure:
            return None
        return UNIT_MAPPING.get(measure["unit"], measure["unitName"])

    @property
    def native_value(self):
        """Return the current sensor value."""
        return self.coordinator.data["measures"][self.key]["latestReading"]["value"]