"""Support for WeMo device discovery."""
import asyncio
import logging

import pywemo
import requests
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send

from .const import DOMAIN

# Mapping from Wemo model_name to component.
WEMO_MODEL_DISPATCH = {
    "Bridge": "light",
    "CoffeeMaker": "switch",
    "Dimmer": "light",
    "Humidifier": "fan",
    "Insight": "switch",
    "LightSwitch": "switch",
    "Maker": "switch",
    "Motion": "binary_sensor",
    "Sensor": "binary_sensor",
    "Socket": "switch",
}

_LOGGER = logging.getLogger(__name__)


def coerce_host_port(value):
    """Validate that provided value is either just host or host:port.

    Returns (host, None) or (host, port) respectively.
    """
    host, _, port = value.partition(":")

    if not host:
        raise vol.Invalid("host cannot be empty")

    if port:
        port = cv.port(port)
    else:
        port = None

    return host, port


CONF_STATIC = "static"
CONF_DISCOVERY = "discovery"

DEFAULT_DISCOVERY = True

CONFIG_SCHEMA = vol.Schema(
    {
        DOMAIN: vol.Schema(
            {
                vol.Optional(CONF_STATIC, default=[]): vol.Schema(
                    [vol.All(cv.string, coerce_host_port)]
                ),
                vol.Optional(CONF_DISCOVERY, default=DEFAULT_DISCOVERY): cv.boolean,
            }
        )
    },
    extra=vol.ALLOW_EXTRA,
)


async def async_setup(hass, config):
    """Set up for WeMo devices."""
    hass.data[DOMAIN] = {
        "config": config.get(DOMAIN, {}),
        "registry": None,
        "pending": {},
    }

    if DOMAIN in config:
        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 a wemo config entry."""
    config = hass.data[DOMAIN].pop("config")

    # Keep track of WeMo device subscriptions for push updates
    registry = hass.data[DOMAIN]["registry"] = pywemo.SubscriptionRegistry()
    await hass.async_add_executor_job(registry.start)

    def stop_wemo(event):
        """Shutdown Wemo subscriptions and subscription thread on exit."""
        _LOGGER.debug("Shutting down WeMo event subscriptions")
        registry.stop()

    hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_wemo)

    devices = {}

    static_conf = config.get(CONF_STATIC, [])
    if static_conf:
        _LOGGER.debug("Adding statically configured WeMo devices...")
        for device in await asyncio.gather(
            *[
                hass.async_add_executor_job(validate_static_config, host, port)
                for host, port in static_conf
            ]
        ):
            if device is None:
                continue

            devices.setdefault(device.serialnumber, device)

    if config.get(CONF_DISCOVERY, DEFAULT_DISCOVERY):
        _LOGGER.debug("Scanning network for WeMo devices...")
        for device in await hass.async_add_executor_job(pywemo.discover_devices):
            devices.setdefault(
                device.serialnumber,
                device,
            )

    loaded_components = set()

    for device in devices.values():
        _LOGGER.debug(
            "Adding WeMo device at %s:%i (%s)",
            device.host,
            device.port,
            device.serialnumber,
        )

        component = WEMO_MODEL_DISPATCH.get(device.model_name, "switch")

        # Three cases:
        # - First time we see component, we need to load it and initialize the backlog
        # - Component is being loaded, add to backlog
        # - Component is loaded, backlog is gone, dispatch discovery

        if component not in loaded_components:
            hass.data[DOMAIN]["pending"][component] = [device]
            loaded_components.add(component)
            hass.async_create_task(
                hass.config_entries.async_forward_entry_setup(entry, component)
            )

        elif component in hass.data[DOMAIN]["pending"]:
            hass.data[DOMAIN]["pending"][component].append(device)

        else:
            async_dispatcher_send(
                hass,
                f"{DOMAIN}.{component}",
                device,
            )

    return True


def validate_static_config(host, port):
    """Handle a static config."""
    url = setup_url_for_address(host, port)

    if not url:
        _LOGGER.error(
            "Unable to get description url for WeMo at: %s",
            f"{host}:{port}" if port else host,
        )
        return None

    try:
        device = pywemo.discovery.device_from_description(url, None)
    except (
        requests.exceptions.ConnectionError,
        requests.exceptions.Timeout,
    ) as err:
        _LOGGER.error("Unable to access WeMo at %s (%s)", url, err)
        return None

    return device


def setup_url_for_address(host, port):
    """Determine setup.xml url for given host and port pair."""
    if not port:
        port = pywemo.ouimeaux_device.probe_wemo(host)

    if not port:
        return None

    return f"http://{host}:{port}/setup.xml"