"""Native Home Assistant iOS app component."""
import datetime
from http import HTTPStatus

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.components.http import HomeAssistantView
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.util.json import load_json, save_json

from .const import (
    CONF_ACTION_BACKGROUND_COLOR,
    CONF_ACTION_ICON,
    CONF_ACTION_ICON_COLOR,
    CONF_ACTION_ICON_ICON,
    CONF_ACTION_LABEL,
    CONF_ACTION_LABEL_COLOR,
    CONF_ACTION_LABEL_TEXT,
    CONF_ACTION_NAME,
    CONF_ACTIONS,
    DOMAIN,
)

CONF_PUSH = "push"
CONF_PUSH_CATEGORIES = "categories"
CONF_PUSH_CATEGORIES_NAME = "name"
CONF_PUSH_CATEGORIES_IDENTIFIER = "identifier"
CONF_PUSH_CATEGORIES_ACTIONS = "actions"

CONF_PUSH_ACTIONS_IDENTIFIER = "identifier"
CONF_PUSH_ACTIONS_TITLE = "title"
CONF_PUSH_ACTIONS_ACTIVATION_MODE = "activationMode"
CONF_PUSH_ACTIONS_AUTHENTICATION_REQUIRED = "authenticationRequired"
CONF_PUSH_ACTIONS_DESTRUCTIVE = "destructive"
CONF_PUSH_ACTIONS_BEHAVIOR = "behavior"
CONF_PUSH_ACTIONS_CONTEXT = "context"
CONF_PUSH_ACTIONS_TEXT_INPUT_BUTTON_TITLE = "textInputButtonTitle"
CONF_PUSH_ACTIONS_TEXT_INPUT_PLACEHOLDER = "textInputPlaceholder"

CONF_USER = "user"

ATTR_FOREGROUND = "foreground"
ATTR_BACKGROUND = "background"

ACTIVATION_MODES = [ATTR_FOREGROUND, ATTR_BACKGROUND]

ATTR_DEFAULT_BEHAVIOR = "default"
ATTR_TEXT_INPUT_BEHAVIOR = "textInput"

BEHAVIORS = [ATTR_DEFAULT_BEHAVIOR, ATTR_TEXT_INPUT_BEHAVIOR]

ATTR_LAST_SEEN_AT = "lastSeenAt"

ATTR_DEVICE = "device"
ATTR_PUSH_TOKEN = "pushToken"
ATTR_APP = "app"
ATTR_PERMISSIONS = "permissions"
ATTR_PUSH_ID = "pushId"
ATTR_DEVICE_ID = "deviceId"
ATTR_PUSH_SOUNDS = "pushSounds"
ATTR_BATTERY = "battery"

ATTR_DEVICE_NAME = "name"
ATTR_DEVICE_LOCALIZED_MODEL = "localizedModel"
ATTR_DEVICE_MODEL = "model"
ATTR_DEVICE_PERMANENT_ID = "permanentID"
ATTR_DEVICE_SYSTEM_VERSION = "systemVersion"
ATTR_DEVICE_TYPE = "type"
ATTR_DEVICE_SYSTEM_NAME = "systemName"

ATTR_APP_BUNDLE_IDENTIFIER = "bundleIdentifier"
ATTR_APP_BUILD_NUMBER = "buildNumber"
ATTR_APP_VERSION_NUMBER = "versionNumber"

ATTR_LOCATION_PERMISSION = "location"
ATTR_NOTIFICATIONS_PERMISSION = "notifications"

PERMISSIONS = [ATTR_LOCATION_PERMISSION, ATTR_NOTIFICATIONS_PERMISSION]

ATTR_BATTERY_STATE = "state"
ATTR_BATTERY_LEVEL = "level"

ATTR_BATTERY_STATE_UNPLUGGED = "Not Charging"
ATTR_BATTERY_STATE_CHARGING = "Charging"
ATTR_BATTERY_STATE_FULL = "Full"
ATTR_BATTERY_STATE_UNKNOWN = "Unknown"

BATTERY_STATES = [
    ATTR_BATTERY_STATE_UNPLUGGED,
    ATTR_BATTERY_STATE_CHARGING,
    ATTR_BATTERY_STATE_FULL,
    ATTR_BATTERY_STATE_UNKNOWN,
]

ATTR_DEVICES = "devices"

PUSH_ACTION_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_PUSH_ACTIONS_IDENTIFIER): vol.Upper,
        vol.Required(CONF_PUSH_ACTIONS_TITLE): cv.string,
        vol.Optional(
            CONF_PUSH_ACTIONS_ACTIVATION_MODE, default=ATTR_BACKGROUND
        ): vol.In(ACTIVATION_MODES),
        vol.Optional(
            CONF_PUSH_ACTIONS_AUTHENTICATION_REQUIRED, default=False
        ): cv.boolean,
        vol.Optional(CONF_PUSH_ACTIONS_DESTRUCTIVE, default=False): cv.boolean,
        vol.Optional(CONF_PUSH_ACTIONS_BEHAVIOR, default=ATTR_DEFAULT_BEHAVIOR): vol.In(
            BEHAVIORS
        ),
        vol.Optional(CONF_PUSH_ACTIONS_TEXT_INPUT_BUTTON_TITLE): cv.string,
        vol.Optional(CONF_PUSH_ACTIONS_TEXT_INPUT_PLACEHOLDER): cv.string,
    },
    extra=vol.ALLOW_EXTRA,
)

PUSH_ACTION_LIST_SCHEMA = vol.All(cv.ensure_list, [PUSH_ACTION_SCHEMA])

PUSH_CATEGORY_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_PUSH_CATEGORIES_NAME): cv.string,
        vol.Required(CONF_PUSH_CATEGORIES_IDENTIFIER): vol.Lower,
        vol.Required(CONF_PUSH_CATEGORIES_ACTIONS): PUSH_ACTION_LIST_SCHEMA,
    }
)

PUSH_CATEGORY_LIST_SCHEMA = vol.All(cv.ensure_list, [PUSH_CATEGORY_SCHEMA])

ACTION_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_ACTION_NAME): cv.string,
        vol.Optional(CONF_ACTION_BACKGROUND_COLOR): cv.string,
        vol.Optional(CONF_ACTION_LABEL): {
            vol.Optional(CONF_ACTION_LABEL_TEXT): cv.string,
            vol.Optional(CONF_ACTION_LABEL_COLOR): cv.string,
        },
        vol.Optional(CONF_ACTION_ICON): {
            vol.Optional(CONF_ACTION_ICON_ICON): cv.string,
            vol.Optional(CONF_ACTION_ICON_COLOR): cv.string,
        },
    },
)

ACTION_LIST_SCHEMA = vol.All(cv.ensure_list, [ACTION_SCHEMA])

CONFIG_SCHEMA = vol.Schema(
    {
        DOMAIN: {
            CONF_PUSH: {CONF_PUSH_CATEGORIES: PUSH_CATEGORY_LIST_SCHEMA},
            CONF_ACTIONS: ACTION_LIST_SCHEMA,
        }
    },
    extra=vol.ALLOW_EXTRA,
)

IDENTIFY_DEVICE_SCHEMA = vol.Schema(
    {
        vol.Required(ATTR_DEVICE_NAME): cv.string,
        vol.Required(ATTR_DEVICE_LOCALIZED_MODEL): cv.string,
        vol.Required(ATTR_DEVICE_MODEL): cv.string,
        vol.Required(ATTR_DEVICE_PERMANENT_ID): cv.string,
        vol.Required(ATTR_DEVICE_SYSTEM_VERSION): cv.string,
        vol.Required(ATTR_DEVICE_TYPE): cv.string,
        vol.Required(ATTR_DEVICE_SYSTEM_NAME): cv.string,
    },
    extra=vol.ALLOW_EXTRA,
)

IDENTIFY_DEVICE_SCHEMA_CONTAINER = vol.All(dict, IDENTIFY_DEVICE_SCHEMA)

IDENTIFY_APP_SCHEMA = vol.Schema(
    {
        vol.Required(ATTR_APP_BUNDLE_IDENTIFIER): cv.string,
        vol.Required(ATTR_APP_BUILD_NUMBER): cv.positive_int,
        vol.Optional(ATTR_APP_VERSION_NUMBER): cv.string,
    },
    extra=vol.ALLOW_EXTRA,
)

IDENTIFY_APP_SCHEMA_CONTAINER = vol.All(dict, IDENTIFY_APP_SCHEMA)

IDENTIFY_BATTERY_SCHEMA = vol.Schema(
    {
        vol.Required(ATTR_BATTERY_LEVEL): cv.positive_int,
        vol.Required(ATTR_BATTERY_STATE): vol.In(BATTERY_STATES),
    },
    extra=vol.ALLOW_EXTRA,
)

IDENTIFY_BATTERY_SCHEMA_CONTAINER = vol.All(dict, IDENTIFY_BATTERY_SCHEMA)

IDENTIFY_SCHEMA = vol.Schema(
    {
        vol.Required(ATTR_DEVICE): IDENTIFY_DEVICE_SCHEMA_CONTAINER,
        vol.Required(ATTR_BATTERY): IDENTIFY_BATTERY_SCHEMA_CONTAINER,
        vol.Required(ATTR_PUSH_TOKEN): cv.string,
        vol.Required(ATTR_APP): IDENTIFY_APP_SCHEMA_CONTAINER,
        vol.Required(ATTR_PERMISSIONS): vol.All(cv.ensure_list, [vol.In(PERMISSIONS)]),
        vol.Required(ATTR_PUSH_ID): cv.string,
        vol.Required(ATTR_DEVICE_ID): cv.string,
        vol.Optional(ATTR_PUSH_SOUNDS): list,
    },
    extra=vol.ALLOW_EXTRA,
)

CONFIGURATION_FILE = ".ios.conf"


def devices_with_push(hass):
    """Return a dictionary of push enabled targets."""
    return {
        device_name: device.get(ATTR_PUSH_ID)
        for device_name, device in hass.data[DOMAIN][ATTR_DEVICES].items()
        if device.get(ATTR_PUSH_ID) is not None
    }


def enabled_push_ids(hass):
    """Return a list of push enabled target push IDs."""
    return [
        device.get(ATTR_PUSH_ID)
        for device in hass.data[DOMAIN][ATTR_DEVICES].values()
        if device.get(ATTR_PUSH_ID) is not None
    ]


def devices(hass):
    """Return a dictionary of all identified devices."""
    return hass.data[DOMAIN][ATTR_DEVICES]


def device_name_for_push_id(hass, push_id):
    """Return the device name for the push ID."""
    for device_name, device in hass.data[DOMAIN][ATTR_DEVICES].items():
        if device.get(ATTR_PUSH_ID) is push_id:
            return device_name
    return None


async def async_setup(hass, config):
    """Set up the iOS component."""
    conf = config.get(DOMAIN)

    ios_config = await hass.async_add_executor_job(
        load_json, hass.config.path(CONFIGURATION_FILE)
    )

    if ios_config == {}:
        ios_config[ATTR_DEVICES] = {}

    ios_config[CONF_USER] = conf or {}

    if CONF_PUSH not in ios_config[CONF_USER]:
        ios_config[CONF_USER][CONF_PUSH] = {}

    hass.data[DOMAIN] = ios_config

    # No entry support for notify component yet
    discovery.load_platform(hass, "notify", DOMAIN, {}, config)

    if conf is not None:
        hass.async_create_task(
            hass.config_entries.flow.async_init(
                DOMAIN, context={"source": config_entries.SOURCE_IMPORT}
            )
        )

    return True


async def async_setup_entry(hass, entry):
    """Set up an iOS entry."""
    hass.async_create_task(
        hass.config_entries.async_forward_entry_setup(entry, "sensor")
    )

    hass.http.register_view(iOSIdentifyDeviceView(hass.config.path(CONFIGURATION_FILE)))
    hass.http.register_view(iOSPushConfigView(hass.data[DOMAIN][CONF_USER][CONF_PUSH]))
    hass.http.register_view(iOSConfigView(hass.data[DOMAIN][CONF_USER]))

    return True


# pylint: disable=invalid-name
class iOSPushConfigView(HomeAssistantView):
    """A view that provides the push categories configuration."""

    url = "/api/ios/push"
    name = "api:ios:push"

    def __init__(self, push_config):
        """Init the view."""
        self.push_config = push_config

    @callback
    def get(self, request):
        """Handle the GET request for the push configuration."""
        return self.json(self.push_config)


class iOSConfigView(HomeAssistantView):
    """A view that provides the whole user-defined configuration."""

    url = "/api/ios/config"
    name = "api:ios:config"

    def __init__(self, config):
        """Init the view."""
        self.config = config

    @callback
    def get(self, request):
        """Handle the GET request for the user-defined configuration."""
        return self.json(self.config)


class iOSIdentifyDeviceView(HomeAssistantView):
    """A view that accepts device identification requests."""

    url = "/api/ios/identify"
    name = "api:ios:identify"

    def __init__(self, config_path):
        """Initialize the view."""
        self._config_path = config_path

    async def post(self, request):
        """Handle the POST request for device identification."""
        try:
            data = await request.json()
        except ValueError:
            return self.json_message("Invalid JSON", HTTPStatus.BAD_REQUEST)

        hass = request.app["hass"]

        data[ATTR_LAST_SEEN_AT] = datetime.datetime.now().isoformat()

        device_id = data[ATTR_DEVICE_ID]

        hass.data[DOMAIN][ATTR_DEVICES][device_id] = data

        async_dispatcher_send(hass, f"{DOMAIN}.{device_id}", data)

        try:
            save_json(self._config_path, hass.data[DOMAIN])
        except HomeAssistantError:
            return self.json_message(
                "Error saving device.", HTTPStatus.INTERNAL_SERVER_ERROR
            )

        return self.json({"status": "registered"})
