"""
Native Home Assistant iOS app component.

For more details about this component, please refer to the documentation at
https://home-assistant.io/ecosystem/ios/
"""
import asyncio
import os
import json
import logging
import datetime

import voluptuous as vol
# from voluptuous.humanize import humanize_error

from homeassistant.helpers import config_validation as cv

from homeassistant.helpers import discovery

from homeassistant.core import callback

from homeassistant.components.http import HomeAssistantView

from homeassistant.remote import JSONEncoder

from homeassistant.const import (HTTP_INTERNAL_SERVER_ERROR,
                                 HTTP_BAD_REQUEST)

_LOGGER = logging.getLogger(__name__)

DOMAIN = 'ios'

DEPENDENCIES = ['device_tracker', 'http', 'zeroconf']

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'

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_IDENTIFER = 'bundleIdentifer'
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 = 'Unplugged'
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'

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)

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

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: {
        CONF_PUSH: {
            CONF_PUSH_CATEGORIES: vol.All(cv.ensure_list, [{
                vol.Required(CONF_PUSH_CATEGORIES_NAME): cv.string,
                vol.Required(CONF_PUSH_CATEGORIES_IDENTIFIER): vol.Upper,
                vol.Required(CONF_PUSH_CATEGORIES_ACTIONS): ACTION_SCHEMA_LIST
            }])
        }
    }
}, 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_IDENTIFER): 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'

CONFIG_FILE = {ATTR_DEVICES: {}}

CONFIG_FILE_PATH = ""


def _load_config(filename):
    """Load configuration."""
    if not os.path.isfile(filename):
        return {}

    try:
        with open(filename, "r") as fdesc:
            inp = fdesc.read()

        # In case empty file
        if not inp:
            return {}

        return json.loads(inp)
    except (IOError, ValueError) as error:
        _LOGGER.error("Reading config file %s failed: %s", filename, error)
        return None


def _save_config(filename, config):
    """Save configuration."""
    try:
        with open(filename, 'w') as fdesc:
            fdesc.write(json.dumps(config, cls=JSONEncoder))
    except (IOError, TypeError) as error:
        _LOGGER.error("Saving config file failed: %s", error)
        return False
    return True


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


def enabled_push_ids():
    """Return a list of push enabled target push IDs."""
    push_ids = list()
    # pylint: disable=unused-variable
    for device_name, device in CONFIG_FILE[ATTR_DEVICES].items():
        if device.get(ATTR_PUSH_ID) is not None:
            push_ids.append(device.get(ATTR_PUSH_ID))
    return push_ids


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


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


def setup(hass, config):
    """Set up the iOS component."""
    # pylint: disable=global-statement, import-error
    global CONFIG_FILE
    global CONFIG_FILE_PATH

    CONFIG_FILE_PATH = hass.config.path(CONFIGURATION_FILE)

    CONFIG_FILE = _load_config(CONFIG_FILE_PATH)

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

    discovery.load_platform(hass, 'notify', DOMAIN, {}, config)

    discovery.load_platform(hass, 'sensor', DOMAIN, {}, config)

    hass.http.register_view(iOSIdentifyDeviceView)

    app_config = config.get(DOMAIN, {})
    hass.http.register_view(iOSPushConfigView(app_config.get(CONF_PUSH, {})))

    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 iOSIdentifyDeviceView(HomeAssistantView):
    """A view that accepts device identification requests."""

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

    @asyncio.coroutine
    def post(self, request):
        """Handle the POST request for device identification."""
        try:
            data = yield from request.json()
        except ValueError:
            return self.json_message("Invalid JSON", HTTP_BAD_REQUEST)

        # Commented for now while iOS app is getting frequent updates
        # try:
        #     data = IDENTIFY_SCHEMA(req_data)
        # except vol.Invalid as ex:
        #     return self.json_message(humanize_error(request.json, ex),
        #                              HTTP_BAD_REQUEST)

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

        name = data.get(ATTR_DEVICE_ID)

        CONFIG_FILE[ATTR_DEVICES][name] = data

        if not _save_config(CONFIG_FILE_PATH, CONFIG_FILE):
            return self.json_message("Error saving device.",
                                     HTTP_INTERNAL_SERVER_ERROR)

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