"""Config flow for Smappee."""
import logging

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_IP_ADDRESS
from homeassistant.helpers import config_entry_oauth2_flow

from . import api
from .const import (
    CONF_HOSTNAME,
    CONF_SERIALNUMBER,
    DOMAIN,
    ENV_CLOUD,
    ENV_LOCAL,
    SUPPORTED_LOCAL_DEVICES,
)

_LOGGER = logging.getLogger(__name__)


class SmappeeFlowHandler(
    config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
):
    """Config Smappee config flow."""

    DOMAIN = DOMAIN
    CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL

    async def async_oauth_create_entry(self, data):
        """Create an entry for the flow."""

        await self.async_set_unique_id(unique_id=f"{DOMAIN}Cloud")
        return self.async_create_entry(title=f"{DOMAIN}Cloud", data=data)

    @property
    def logger(self) -> logging.Logger:
        """Return logger."""
        return logging.getLogger(__name__)

    async def async_step_zeroconf(self, discovery_info):
        """Handle zeroconf discovery."""

        if not discovery_info[CONF_HOSTNAME].startswith(SUPPORTED_LOCAL_DEVICES):
            # We currently only support Energy and Solar models (legacy)
            return self.async_abort(reason="invalid_mdns")

        serial_number = (
            discovery_info[CONF_HOSTNAME].replace(".local.", "").replace("Smappee", "")
        )

        # Check if already configured (local)
        await self.async_set_unique_id(serial_number)
        self._abort_if_unique_id_configured()

        # Check if already configured (cloud)
        if self.is_cloud_device_already_added():
            return self.async_abort(reason="already_configured_device")

        # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
        self.context.update(
            {
                CONF_IP_ADDRESS: discovery_info["host"],
                CONF_SERIALNUMBER: serial_number,
                "title_placeholders": {"name": serial_number},
            }
        )

        return await self.async_step_zeroconf_confirm()

    async def async_step_zeroconf_confirm(self, user_input=None):
        """Confirm zeroconf flow."""
        errors = {}

        # Check if already configured (cloud)
        if self.is_cloud_device_already_added():
            return self.async_abort(reason="already_configured_device")

        if user_input is None:
            # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
            serialnumber = self.context.get(CONF_SERIALNUMBER)
            return self.async_show_form(
                step_id="zeroconf_confirm",
                description_placeholders={"serialnumber": serialnumber},
                errors=errors,
            )

        # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
        ip_address = self.context.get(CONF_IP_ADDRESS)
        serial_number = self.context.get(CONF_SERIALNUMBER)

        # Attempt to make a connection to the local device
        smappee_api = api.api.SmappeeLocalApi(ip=ip_address)
        logon = await self.hass.async_add_executor_job(smappee_api.logon)
        if logon is None:
            return self.async_abort(reason="connection_error")

        return self.async_create_entry(
            title=f"{DOMAIN}{serial_number}",
            data={CONF_IP_ADDRESS: ip_address, CONF_SERIALNUMBER: serial_number},
        )

    async def async_step_user(self, user_input=None):
        """Handle a flow initiated by the user."""

        # If there is a CLOUD entry already, abort a new LOCAL entry
        if self.is_cloud_device_already_added():
            return self.async_abort(reason="already_configured_device")

        return await self.async_step_environment()

    async def async_step_environment(self, user_input=None):
        """Decide environment, cloud or local."""
        if user_input is None:
            return self.async_show_form(
                step_id="environment",
                data_schema=vol.Schema(
                    {
                        vol.Required("environment", default=ENV_CLOUD): vol.In(
                            [ENV_CLOUD, ENV_LOCAL]
                        )
                    }
                ),
                errors={},
            )

        # Environment chosen, request additional host information for LOCAL or OAuth2 flow for CLOUD
        # Ask for host detail
        if user_input["environment"] == ENV_LOCAL:
            return await self.async_step_local()

        # Abort cloud option if a LOCAL entry has already been added
        if user_input["environment"] == ENV_CLOUD and self._async_current_entries():
            return self.async_abort(reason="already_configured_local_device")

        return await self.async_step_pick_implementation()

    async def async_step_local(self, user_input=None):
        """Handle local flow."""
        if user_input is None:
            return self.async_show_form(
                step_id="local",
                data_schema=vol.Schema({vol.Required(CONF_HOST): str}),
                errors={},
            )
        # In a LOCAL setup we still need to resolve the host to serial number
        ip_address = user_input["host"]
        smappee_api = api.api.SmappeeLocalApi(ip=ip_address)
        logon = await self.hass.async_add_executor_job(smappee_api.logon)
        if logon is None:
            return self.async_abort(reason="connection_error")

        advanced_config = await self.hass.async_add_executor_job(
            smappee_api.load_advanced_config
        )
        serial_number = None
        for config_item in advanced_config:
            if config_item["key"] == "mdnsHostName":
                serial_number = config_item["value"]

        if serial_number is None or not serial_number.startswith(
            SUPPORTED_LOCAL_DEVICES
        ):
            # We currently only support Energy and Solar models (legacy)
            return self.async_abort(reason="invalid_mdns")

        serial_number = serial_number.replace("Smappee", "")

        # Check if already configured (local)
        await self.async_set_unique_id(serial_number, raise_on_progress=False)
        self._abort_if_unique_id_configured()

        return self.async_create_entry(
            title=f"{DOMAIN}{serial_number}",
            data={CONF_IP_ADDRESS: ip_address, CONF_SERIALNUMBER: serial_number},
        )

    def is_cloud_device_already_added(self):
        """Check if a CLOUD device has already been added."""
        for entry in self._async_current_entries():
            if entry.unique_id is not None and entry.unique_id == f"{DOMAIN}Cloud":
                return True
        return False
