"""
Support for deCONZ devices.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/deconz/
"""
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import (
    CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.util.json import load_json

# Loading the config flow file will register the flow
from .config_flow import configured_hosts
from .const import CONFIG_FILE, DOMAIN, _LOGGER
from .gateway import DeconzGateway

REQUIREMENTS = ['pydeconz==47']

SUPPORTED_PLATFORMS = ['binary_sensor', 'cover',
                       'light', 'scene', 'sensor', 'switch']

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.Schema({
        vol.Optional(CONF_API_KEY): cv.string,
        vol.Optional(CONF_HOST): cv.string,
        vol.Optional(CONF_PORT, default=80): cv.port,
    })
}, extra=vol.ALLOW_EXTRA)

SERVICE_DECONZ = 'configure'

SERVICE_FIELD = 'field'
SERVICE_ENTITY = 'entity'
SERVICE_DATA = 'data'

SERVICE_SCHEMA = vol.All(vol.Schema({
    vol.Optional(SERVICE_ENTITY): cv.entity_id,
    vol.Optional(SERVICE_FIELD): cv.matches_regex('/.*'),
    vol.Required(SERVICE_DATA): dict,
}), cv.has_at_least_one_key(SERVICE_ENTITY, SERVICE_FIELD))

SERVICE_DEVICE_REFRESH = 'device_refresh'


async def async_setup(hass, config):
    """Load configuration for deCONZ component.

    Discovery has loaded the component if DOMAIN is not present in config.
    """
    if DOMAIN in config:
        deconz_config = None
        config_file = await hass.async_add_job(
            load_json, hass.config.path(CONFIG_FILE))
        if config_file:
            deconz_config = config_file
        elif CONF_HOST in config[DOMAIN]:
            deconz_config = config[DOMAIN]
        if deconz_config and not configured_hosts(hass):
            hass.async_add_job(hass.config_entries.flow.async_init(
                DOMAIN,
                context={'source': config_entries.SOURCE_IMPORT},
                data=deconz_config
            ))
    return True


async def async_setup_entry(hass, config_entry):
    """Set up a deCONZ bridge for a config entry.

    Load config, group, light and sensor data for server information.
    Start websocket for push notification of state changes from deCONZ.
    """
    if DOMAIN in hass.data:
        _LOGGER.error(
            "Config entry failed since one deCONZ instance already exists")
        return False

    gateway = DeconzGateway(hass, config_entry)

    hass.data[DOMAIN] = gateway

    if not await gateway.async_setup():
        return False

    device_registry = await \
        hass.helpers.device_registry.async_get_registry()
    device_registry.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        connections={(CONNECTION_NETWORK_MAC, gateway.api.config.mac)},
        identifiers={(DOMAIN, gateway.api.config.bridgeid)},
        manufacturer='Dresden Elektronik', model=gateway.api.config.modelid,
        name=gateway.api.config.name, sw_version=gateway.api.config.swversion)

    async def async_configure(call):
        """Set attribute of device in deCONZ.

        Entity is used to resolve to a device path (e.g. '/lights/1').
        Field is a string representing either a full path
        (e.g. '/lights/1/state') when entity is not specified, or a
        subpath (e.g. '/state') when used together with entity.
        Data is a json object with what data you want to alter
        e.g. data={'on': true}.
        {
            "field": "/lights/1/state",
            "data": {"on": true}
        }
        See Dresden Elektroniks REST API documentation for details:
        http://dresden-elektronik.github.io/deconz-rest-doc/rest/
        """
        field = call.data.get(SERVICE_FIELD, '')
        entity_id = call.data.get(SERVICE_ENTITY)
        data = call.data.get(SERVICE_DATA)
        gateway = hass.data[DOMAIN]

        if entity_id:
            try:
                field = gateway.deconz_ids[entity_id] + field
            except KeyError:
                _LOGGER.error('Could not find the entity %s', entity_id)
                return

        await gateway.api.async_put_state(field, data)

    hass.services.async_register(
        DOMAIN, SERVICE_DECONZ, async_configure, schema=SERVICE_SCHEMA)

    async def async_refresh_devices(call):
        """Refresh available devices from deCONZ."""
        gateway = hass.data[DOMAIN]

        groups = set(gateway.api.groups.keys())
        lights = set(gateway.api.lights.keys())
        scenes = set(gateway.api.scenes.keys())
        sensors = set(gateway.api.sensors.keys())

        if not await gateway.api.async_load_parameters():
            return

        gateway.async_add_device_callback(
            'group', [group
                      for group_id, group in gateway.api.groups.items()
                      if group_id not in groups]
        )

        gateway.async_add_device_callback(
            'light', [light
                      for light_id, light in gateway.api.lights.items()
                      if light_id not in lights]
        )

        gateway.async_add_device_callback(
            'scene', [scene
                      for scene_id, scene in gateway.api.scenes.items()
                      if scene_id not in scenes]
        )

        gateway.async_add_device_callback(
            'sensor', [sensor
                       for sensor_id, sensor in gateway.api.sensors.items()
                       if sensor_id not in sensors]
        )

    hass.services.async_register(
        DOMAIN, SERVICE_DEVICE_REFRESH, async_refresh_devices)

    hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gateway.shutdown)
    return True


async def async_unload_entry(hass, config_entry):
    """Unload deCONZ config entry."""
    gateway = hass.data.pop(DOMAIN)
    hass.services.async_remove(DOMAIN, SERVICE_DECONZ)
    hass.services.async_remove(DOMAIN, SERVICE_DEVICE_REFRESH)
    return await gateway.async_reset()
