"""Support for guages from flood monitoring API."""
from datetime import timedelta
import logging

from aioeafm import get_station
import async_timeout

from homeassistant.components.sensor import SensorEntity
from homeassistant.const import ATTR_ATTRIBUTION, LENGTH_METERS
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import (
    CoordinatorEntity,
    DataUpdateCoordinator,
)

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

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


def get_measures(station_data):
    """Force measure key to always be a list."""
    if "measures" not in station_data:
        return []
    if isinstance(station_data["measures"], dict):
        return [station_data["measures"]]
    return station_data["measures"]


async def async_setup_entry(hass, config_entry, async_add_entities):
    """Set up UK Flood Monitoring Sensors."""
    station_key = config_entry.data["station"]
    session = async_get_clientsession(hass=hass)

    measurements = set()

    async def async_update_data():
        # DataUpdateCoordinator will handle aiohttp ClientErrors and timouts
        async with async_timeout.timeout(30):
            data = await get_station(session, station_key)

        measures = get_measures(data)
        entities = []

        # Look to see if payload contains new measures
        for measure in measures:
            if measure["@id"] in measurements:
                continue

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

            entities.append(Measurement(hass.data[DOMAIN][station_key], measure["@id"]))
            measurements.add(measure["@id"])

        async_add_entities(entities)

        # Turn data.measures into a dict rather than a list so easier for entities to
        # find themselves.
        data["measures"] = {measure["@id"]: measure for measure in measures}

        return data

    hass.data[DOMAIN][station_key] = coordinator = DataUpdateCoordinator(
        hass,
        _LOGGER,
        name="sensor",
        update_method=async_update_data,
        update_interval=timedelta(seconds=15 * 60),
    )

    # Fetch initial data so we have data when entities subscribe
    await coordinator.async_refresh()


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

    attribution = "This uses Environment Agency flood and river level data from the real-time data API"

    def __init__(self, coordinator, key):
        """Initialise the gauge with a data instance and station."""
        super().__init__(coordinator)
        self.key = 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 name(self):
        """Return the name of the gauge."""
        return f"{self.station_name} {self.parameter_name} {self.qualifier}"

    @property
    def unique_id(self):
        """Return the unique id of the gauge."""
        return self.key

    @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=self.name,
        )

    @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 extra_state_attributes(self):
        """Return the sensor specific state attributes."""
        return {ATTR_ATTRIBUTION: self.attribution}

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