"""Config flow to configure SmartThings."""
import logging

from aiohttp.client_exceptions import ClientResponseError
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import (
    CONF_APP_ID, CONF_INSTALLED_APP_ID, CONF_LOCATION_ID, DOMAIN,
    VAL_UID_MATCHER)
from .smartapp import (
    create_app, find_app, setup_smartapp, setup_smartapp_endpoint, update_app)

_LOGGER = logging.getLogger(__name__)


@config_entries.HANDLERS.register(DOMAIN)
class SmartThingsFlowHandler(config_entries.ConfigFlow):
    """
    Handle configuration of SmartThings integrations.

    Any number of integrations are supported.  The high level flow follows:
    1) Flow initiated
        a) User initiates through the UI
        b) Re-configuration of a failed entry setup
    2) Enter access token
        a) Check not already setup
        b) Validate format
        c) Setup SmartApp
    3) Wait for Installation
        a) Check user installed into one or more locations
        b) Config entries setup for all installations
    """

    VERSION = 1
    CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH

    def __init__(self):
        """Create a new instance of the flow handler."""
        self.access_token = None
        self.app_id = None
        self.api = None

    async def async_step_import(self, user_input=None):
        """Occurs when a previously entry setup fails and is re-initiated."""
        return await self.async_step_user(user_input)

    async def async_step_user(self, user_input=None):
        """Get access token and validate it."""
        from pysmartthings import SmartThings

        errors = {}
        if not self.hass.config.api.base_url.lower().startswith('https://'):
            errors['base'] = "base_url_not_https"
            return self._show_step_user(errors)

        if user_input is None or CONF_ACCESS_TOKEN not in user_input:
            return self._show_step_user(errors)

        self.access_token = user_input.get(CONF_ACCESS_TOKEN, '')
        self.api = SmartThings(async_get_clientsession(self.hass),
                               self.access_token)

        # Ensure token is a UUID
        if not VAL_UID_MATCHER.match(self.access_token):
            errors[CONF_ACCESS_TOKEN] = "token_invalid_format"
            return self._show_step_user(errors)
        # Check not already setup in another entry
        if any(entry.data.get(CONF_ACCESS_TOKEN) == self.access_token
               for entry
               in self.hass.config_entries.async_entries(DOMAIN)):
            errors[CONF_ACCESS_TOKEN] = "token_already_setup"
            return self._show_step_user(errors)

        # Setup end-point
        await setup_smartapp_endpoint(self.hass)

        try:
            app = await find_app(self.hass, self.api)
            if app:
                await app.refresh()  # load all attributes
                await update_app(self.hass, app)
            else:
                app = await create_app(self.hass, self.api)
            setup_smartapp(self.hass, app)
            self.app_id = app.app_id
        except ClientResponseError as ex:
            if ex.status == 401:
                errors[CONF_ACCESS_TOKEN] = "token_unauthorized"
            elif ex.status == 403:
                errors[CONF_ACCESS_TOKEN] = "token_forbidden"
            else:
                errors['base'] = "app_setup_error"
            return self._show_step_user(errors)
        except Exception:  # pylint:disable=broad-except
            errors['base'] = "app_setup_error"
            _LOGGER.exception("Unexpected error setting up the SmartApp")
            return self._show_step_user(errors)

        return await self.async_step_wait_install()

    async def async_step_wait_install(self, user_input=None):
        """Wait for SmartApp installation."""
        from pysmartthings import InstalledAppStatus

        errors = {}
        if user_input is None:
            return self._show_step_wait_install(errors)

        # Find installed apps that were authorized
        installed_apps = [app for app in await self.api.installed_apps(
            installed_app_status=InstalledAppStatus.AUTHORIZED)
                          if app.app_id == self.app_id]
        if not installed_apps:
            errors['base'] = 'app_not_installed'
            return self._show_step_wait_install(errors)

        # User may have installed the SmartApp in more than one SmartThings
        # location. Config flows are created for the additional installations
        for installed_app in installed_apps[1:]:
            self.hass.async_create_task(
                self.hass.config_entries.flow.async_init(
                    DOMAIN, context={'source': 'install'},
                    data={
                        CONF_APP_ID: installed_app.app_id,
                        CONF_INSTALLED_APP_ID: installed_app.installed_app_id,
                        CONF_LOCATION_ID: installed_app.location_id,
                        CONF_ACCESS_TOKEN: self.access_token
                    }))

        # return entity for the first one.
        installed_app = installed_apps[0]
        return await self.async_step_install({
            CONF_APP_ID: installed_app.app_id,
            CONF_INSTALLED_APP_ID: installed_app.installed_app_id,
            CONF_LOCATION_ID: installed_app.location_id,
            CONF_ACCESS_TOKEN: self.access_token
        })

    def _show_step_user(self, errors):
        return self.async_show_form(
            step_id='user',
            data_schema=vol.Schema({
                vol.Required(CONF_ACCESS_TOKEN,
                             default=self.access_token): str
            }),
            errors=errors,
            description_placeholders={
                'token_url': 'https://account.smartthings.com/tokens',
                'component_url':
                    'https://www.home-assistant.io/components/smartthings/'
            }
        )

    def _show_step_wait_install(self, errors):
        return self.async_show_form(
            step_id='wait_install',
            errors=errors
        )

    async def async_step_install(self, data=None):
        """
        Create a config entry at completion of a flow.

        Launched when the user completes the flow or when the SmartApp
        is installed into an additional location.
        """
        from pysmartthings import SmartThings

        if not self.api:
            # Launched from the SmartApp install event handler
            self.api = SmartThings(
                async_get_clientsession(self.hass), data[CONF_ACCESS_TOKEN])

        location = await self.api.location(data[CONF_LOCATION_ID])
        return self.async_create_entry(title=location.name, data=data)
