"""
Support for Pollen.com allergen and cold/flu sensors.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.pollen/
"""
import logging
from datetime import timedelta
from statistics import mean

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
    ATTR_ATTRIBUTION, ATTR_STATE, CONF_MONITORED_CONDITIONS
)
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle, slugify

REQUIREMENTS = ['pypollencom==1.1.1']
_LOGGER = logging.getLogger(__name__)

ATTR_ALLERGEN_GENUS = 'primary_allergen_genus'
ATTR_ALLERGEN_NAME = 'primary_allergen_name'
ATTR_ALLERGEN_TYPE = 'primary_allergen_type'
ATTR_CITY = 'city'
ATTR_OUTLOOK = 'outlook'
ATTR_RATING = 'rating'
ATTR_SEASON = 'season'
ATTR_TREND = 'trend'
ATTR_ZIP_CODE = 'zip_code'

CONF_ZIP_CODE = 'zip_code'

DEFAULT_ATTRIBUTION = 'Data provided by IQVIA™'

MIN_TIME_UPDATE_AVERAGES = timedelta(hours=12)
MIN_TIME_UPDATE_INDICES = timedelta(minutes=10)

CONDITIONS = {
    'allergy_average_forecasted': (
        'Allergy Index: Forecasted Average',
        'AllergyAverageSensor',
        'allergy_average_data',
        {'data_attr': 'extended_data'},
        'mdi:flower'
    ),
    'allergy_average_historical': (
        'Allergy Index: Historical Average',
        'AllergyAverageSensor',
        'allergy_average_data',
        {'data_attr': 'historic_data'},
        'mdi:flower'
    ),
    'allergy_index_today': (
        'Allergy Index: Today',
        'AllergyIndexSensor',
        'allergy_index_data',
        {'key': 'Today'},
        'mdi:flower'
    ),
    'allergy_index_tomorrow': (
        'Allergy Index: Tomorrow',
        'AllergyIndexSensor',
        'allergy_index_data',
        {'key': 'Tomorrow'},
        'mdi:flower'
    ),
    'allergy_index_yesterday': (
        'Allergy Index: Yesterday',
        'AllergyIndexSensor',
        'allergy_index_data',
        {'key': 'Yesterday'},
        'mdi:flower'
    ),
    'disease_average_forecasted': (
        'Cold & Flu: Forecasted Average',
        'AllergyAverageSensor',
        'disease_average_data',
        {'data_attr': 'extended_data'},
        'mdi:snowflake'
    )
}

RATING_MAPPING = [{
    'label': 'Low',
    'minimum': 0.0,
    'maximum': 2.4
}, {
    'label': 'Low/Medium',
    'minimum': 2.5,
    'maximum': 4.8
}, {
    'label': 'Medium',
    'minimum': 4.9,
    'maximum': 7.2
}, {
    'label': 'Medium/High',
    'minimum': 7.3,
    'maximum': 9.6
}, {
    'label': 'High',
    'minimum': 9.7,
    'maximum': 12
}]

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_ZIP_CODE): str,
    vol.Required(CONF_MONITORED_CONDITIONS):
        vol.All(cv.ensure_list, [vol.In(CONDITIONS)]),
})


def setup_platform(hass, config, add_devices, discovery_info=None):
    """Configure the platform and add the sensors."""
    from pypollencom import Client

    _LOGGER.debug('Configuration data: %s', config)

    client = Client(config[CONF_ZIP_CODE])
    datas = {
        'allergy_average_data': AllergyAveragesData(client),
        'allergy_index_data': AllergyIndexData(client),
        'disease_average_data': DiseaseData(client)
    }
    classes = {
        'AllergyAverageSensor': AllergyAverageSensor,
        'AllergyIndexSensor': AllergyIndexSensor
    }

    for data in datas.values():
        data.update()

    sensors = []
    for condition in config[CONF_MONITORED_CONDITIONS]:
        name, sensor_class, data_key, params, icon = CONDITIONS[condition]
        sensors.append(classes[sensor_class](
            datas[data_key],
            params,
            name,
            icon,
            config[CONF_ZIP_CODE]
        ))

    add_devices(sensors, True)


def calculate_trend(list_of_nums):
    """Calculate the most common rating as a trend."""
    ratings = list(
        r['label'] for n in list_of_nums
        for r in RATING_MAPPING
        if r['minimum'] <= n <= r['maximum'])
    return max(set(ratings), key=ratings.count)


class BaseSensor(Entity):
    """Define a base class for all of our sensors."""

    def __init__(self, data, data_params, name, icon, unique_id):
        """Initialize the sensor."""
        self._attrs = {}
        self._icon = icon
        self._name = name
        self._data_params = data_params
        self._state = None
        self._unit = None
        self._unique_id = unique_id
        self.data = data

    @property
    def device_state_attributes(self):
        """Return the device state attributes."""
        self._attrs.update({ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION})
        return self._attrs

    @property
    def icon(self):
        """Return the icon."""
        return self._icon

    @property
    def name(self):
        """Return the name."""
        return self._name

    @property
    def state(self):
        """Return the state."""
        return self._state

    @property
    def unique_id(self):
        """Return a unique, HASS-friendly identifier for this entity."""
        return '{0}_{1}'.format(self._unique_id, slugify(self._name))

    @property
    def unit_of_measurement(self):
        """Return the unit the value is expressed in."""
        return self._unit


class AllergyAverageSensor(BaseSensor):
    """Define a sensor to show allergy average information."""

    def update(self):
        """Update the status of the sensor."""
        self.data.update()

        try:
            data_attr = getattr(self.data, self._data_params['data_attr'])
            indices = [p['Index'] for p in data_attr['Location']['periods']]
            self._attrs[ATTR_TREND] = calculate_trend(indices)
        except KeyError:
            _LOGGER.error("Pollen.com API didn't return any data")
            return

        try:
            self._attrs[ATTR_CITY] = data_attr['Location']['City'].title()
            self._attrs[ATTR_STATE] = data_attr['Location']['State']
            self._attrs[ATTR_ZIP_CODE] = data_attr['Location']['ZIP']
        except KeyError:
            _LOGGER.debug('Location data not included in API response')
            self._attrs[ATTR_CITY] = None
            self._attrs[ATTR_STATE] = None
            self._attrs[ATTR_ZIP_CODE] = None

        average = round(mean(indices), 1)
        [rating] = [
            i['label'] for i in RATING_MAPPING
            if i['minimum'] <= average <= i['maximum']
        ]
        self._attrs[ATTR_RATING] = rating

        self._state = average
        self._unit = 'index'


class AllergyIndexSensor(BaseSensor):
    """Define a sensor to show allergy index information."""

    def update(self):
        """Update the status of the sensor."""
        self.data.update()

        try:
            location_data = self.data.current_data['Location']
            [period] = [
                p for p in location_data['periods']
                if p['Type'] == self._data_params['key']
            ]
            [rating] = [
                i['label'] for i in RATING_MAPPING
                if i['minimum'] <= period['Index'] <= i['maximum']
            ]
            self._attrs[ATTR_ALLERGEN_GENUS] = period['Triggers'][0]['Genus']
            self._attrs[ATTR_ALLERGEN_NAME] = period['Triggers'][0]['Name']
            self._attrs[ATTR_ALLERGEN_TYPE] = period['Triggers'][0][
                'PlantType']
            self._attrs[ATTR_RATING] = rating

        except KeyError:
            _LOGGER.error("Pollen.com API didn't return any data")
            return

        try:
            self._attrs[ATTR_CITY] = location_data['City'].title()
            self._attrs[ATTR_STATE] = location_data['State']
            self._attrs[ATTR_ZIP_CODE] = location_data['ZIP']
        except KeyError:
            _LOGGER.debug('Location data not included in API response')
            self._attrs[ATTR_CITY] = None
            self._attrs[ATTR_STATE] = None
            self._attrs[ATTR_ZIP_CODE] = None

        try:
            self._attrs[ATTR_OUTLOOK] = self.data.outlook_data['Outlook']
        except KeyError:
            _LOGGER.debug('Outlook data not included in API response')
            self._attrs[ATTR_OUTLOOK] = None

        try:
            self._attrs[ATTR_SEASON] = self.data.outlook_data['Season']
        except KeyError:
            _LOGGER.debug('Season data not included in API response')
            self._attrs[ATTR_SEASON] = None

        try:
            self._attrs[ATTR_TREND] = self.data.outlook_data['Trend'].title()
        except KeyError:
            _LOGGER.debug('Trend data not included in API response')
            self._attrs[ATTR_TREND] = None

        self._state = period['Index']
        self._unit = 'index'


class DataBase(object):
    """Define a generic data object."""

    def __init__(self, client):
        """Initialize."""
        self._client = client

    def _get_client_data(self, module, operation):
        """Get data from a particular point in the API."""
        from pypollencom.exceptions import HTTPError

        data = {}
        try:
            data = getattr(getattr(self._client, module), operation)()
            _LOGGER.debug('Received "%s_%s" data: %s', module, operation, data)
        except HTTPError as exc:
            _LOGGER.error('An error occurred while retrieving data')
            _LOGGER.debug(exc)

        return data


class AllergyAveragesData(DataBase):
    """Define an object to averages on future and historical allergy data."""

    def __init__(self, client):
        """Initialize."""
        super().__init__(client)
        self.extended_data = None
        self.historic_data = None

    @Throttle(MIN_TIME_UPDATE_AVERAGES)
    def update(self):
        """Update with new data."""
        self.extended_data = self._get_client_data('allergens', 'extended')
        self.historic_data = self._get_client_data('allergens', 'historic')


class AllergyIndexData(DataBase):
    """Define an object to retrieve current allergy index info."""

    def __init__(self, client):
        """Initialize."""
        super().__init__(client)
        self.current_data = None
        self.outlook_data = None

    @Throttle(MIN_TIME_UPDATE_INDICES)
    def update(self):
        """Update with new index data."""
        self.current_data = self._get_client_data('allergens', 'current')
        self.outlook_data = self._get_client_data('allergens', 'outlook')


class DiseaseData(DataBase):
    """Define an object to retrieve current disease index info."""

    def __init__(self, client):
        """Initialize."""
        super().__init__(client)
        self.extended_data = None

    @Throttle(MIN_TIME_UPDATE_INDICES)
    def update(self):
        """Update with new cold/flu data."""
        self.extended_data = self._get_client_data('disease', 'extended')
