"""Support for mobile_app push notifications."""
import asyncio
from datetime import datetime, timezone
import logging

import async_timeout

from homeassistant.components.notify import (
    ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT,
    BaseNotificationService)

from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.util.dt as dt_util

from .const import (ATTR_APP_DATA, ATTR_APP_ID, ATTR_APP_VERSION,
                    ATTR_DEVICE_NAME, ATTR_OS_VERSION, ATTR_PUSH_RATE_LIMITS,
                    ATTR_PUSH_RATE_LIMITS_ERRORS,
                    ATTR_PUSH_RATE_LIMITS_MAXIMUM,
                    ATTR_PUSH_RATE_LIMITS_RESETS_AT,
                    ATTR_PUSH_RATE_LIMITS_SUCCESSFUL, ATTR_PUSH_TOKEN,
                    ATTR_PUSH_URL, DATA_CONFIG_ENTRIES, DOMAIN)

_LOGGER = logging.getLogger(__name__)


def push_registrations(hass):
    """Return a dictionary of push enabled registrations."""
    targets = {}
    for webhook_id, entry in hass.data[DOMAIN][DATA_CONFIG_ENTRIES].items():
        data = entry.data
        app_data = data[ATTR_APP_DATA]
        if ATTR_PUSH_TOKEN in app_data and ATTR_PUSH_URL in app_data:
            device_name = data[ATTR_DEVICE_NAME]
            if device_name in targets:
                _LOGGER.warning("Found duplicate device name %s", device_name)
                continue
            targets[device_name] = webhook_id
    return targets


# pylint: disable=invalid-name
def log_rate_limits(hass, device_name, resp, level=logging.INFO):
    """Output rate limit log line at given level."""
    if ATTR_PUSH_RATE_LIMITS not in resp:
        return

    rate_limits = resp[ATTR_PUSH_RATE_LIMITS]
    resetsAt = rate_limits[ATTR_PUSH_RATE_LIMITS_RESETS_AT]
    resetsAtTime = (dt_util.parse_datetime(resetsAt) -
                    datetime.now(timezone.utc))
    rate_limit_msg = ("mobile_app push notification rate limits for %s: "
                      "%d sent, %d allowed, %d errors, "
                      "resets in %s")
    _LOGGER.log(level, rate_limit_msg,
                device_name,
                rate_limits[ATTR_PUSH_RATE_LIMITS_SUCCESSFUL],
                rate_limits[ATTR_PUSH_RATE_LIMITS_MAXIMUM],
                rate_limits[ATTR_PUSH_RATE_LIMITS_ERRORS],
                str(resetsAtTime).split(".")[0])


async def async_get_service(hass, config, discovery_info=None):
    """Get the mobile_app notification service."""
    session = async_get_clientsession(hass)
    return MobileAppNotificationService(session)


class MobileAppNotificationService(BaseNotificationService):
    """Implement the notification service for mobile_app."""

    def __init__(self, session):
        """Initialize the service."""
        self._session = session

    @property
    def targets(self):
        """Return a dictionary of registered targets."""
        return push_registrations(self.hass)

    async def async_send_message(self, message="", **kwargs):
        """Send a message to the Lambda APNS gateway."""
        data = {ATTR_MESSAGE: message}

        if kwargs.get(ATTR_TITLE) is not None:
            # Remove default title from notifications.
            if kwargs.get(ATTR_TITLE) != ATTR_TITLE_DEFAULT:
                data[ATTR_TITLE] = kwargs.get(ATTR_TITLE)

        targets = kwargs.get(ATTR_TARGET)

        if not targets:
            targets = push_registrations(self.hass).values()

        if kwargs.get(ATTR_DATA) is not None:
            data[ATTR_DATA] = kwargs.get(ATTR_DATA)

        for target in targets:
            entry = self.hass.data[DOMAIN][DATA_CONFIG_ENTRIES][target]
            entry_data = entry.data

            app_data = entry_data[ATTR_APP_DATA]
            push_token = app_data[ATTR_PUSH_TOKEN]
            push_url = app_data[ATTR_PUSH_URL]

            data[ATTR_PUSH_TOKEN] = push_token

            reg_info = {
                ATTR_APP_ID: entry_data[ATTR_APP_ID],
                ATTR_APP_VERSION: entry_data[ATTR_APP_VERSION],
            }
            if ATTR_OS_VERSION in entry_data:
                reg_info[ATTR_OS_VERSION] = entry_data[ATTR_OS_VERSION]

            data['registration_info'] = reg_info

            try:
                with async_timeout.timeout(10):
                    response = await self._session.post(push_url, json=data)
                    result = await response.json()

                if response.status == 201:
                    log_rate_limits(self.hass,
                                    entry_data[ATTR_DEVICE_NAME], result)
                    return

                fallback_error = result.get("errorMessage",
                                            "Unknown error")
                fallback_message = ("Internal server error, "
                                    "please try again later: "
                                    "{}").format(fallback_error)
                message = result.get("message", fallback_message)
                if response.status == 429:
                    _LOGGER.warning(message)
                    log_rate_limits(self.hass,
                                    entry_data[ATTR_DEVICE_NAME],
                                    result, logging.WARNING)
                else:
                    _LOGGER.error(message)

            except asyncio.TimeoutError:
                _LOGGER.error("Timeout sending notification to %s", push_url)
