import logging
import voluptuous as vol
from collections import OrderedDict

from .utils.bridge import load_adapter

from .utils.helpers import get_device_model, get_trv_intigration

from .const import (
    CONF_PROTECT_OVERHEATING,
    CONF_CALIBRATION,
    CONF_CHILD_LOCK,
    CONF_HEAT_AUTO_SWAPPED,
    CONF_HEATER,
    CONF_HOMATICIP,
    CONF_HUMIDITY,
    CONF_MODEL,
    CONF_NO_SYSTEM_MODE_OFF,
    CONF_OFF_TEMPERATURE,
    CONF_OUTDOOR_SENSOR,
    CONF_SENSOR,
    CONF_SENSOR_WINDOW,
    CONF_VALVE_MAINTENANCE,
    CONF_WEATHER,
    CONF_WINDOW_TIMEOUT,
    CONF_CALIBRATION_MODE,
    CONF_TOLERANCE,
    CalibrationMode,
    CalibrationType,
)
from homeassistant import config_entries
from homeassistant.const import CONF_NAME
from homeassistant.core import callback
from homeassistant.helpers import selector
from homeassistant.components.climate.const import HVACMode
from homeassistant.helpers import config_validation as cv


from . import DOMAIN  # pylint:disable=unused-import

_LOGGER = logging.getLogger(__name__)

CALIBRATION_TYPE_SELECTOR = selector.SelectSelector(
    selector.SelectSelectorConfig(
        options=[
            selector.SelectOptionDict(
                value=CalibrationType.TARGET_TEMP_BASED,
                label="Target Temperature Based",
            )
        ],
        mode=selector.SelectSelectorMode.DROPDOWN,
    )
)

CALIBRATION_TYPE_ALL_SELECTOR = selector.SelectSelector(
    selector.SelectSelectorConfig(
        options=[
            selector.SelectOptionDict(
                value=CalibrationType.TARGET_TEMP_BASED,
                label="Target Temperature Based",
            ),
            selector.SelectOptionDict(
                value=CalibrationType.LOCAL_BASED, label="Offset Based"
            ),
        ],
        mode=selector.SelectSelectorMode.DROPDOWN,
    )
)

CALIBRATION_MODE_SELECTOR = selector.SelectSelector(
    selector.SelectSelectorConfig(
        options=[
            selector.SelectOptionDict(value=CalibrationMode.DEFAULT, label="Normal"),
            selector.SelectOptionDict(
                value=CalibrationMode.FIX_CALIBRATION, label="Agressive"
            ),
            selector.SelectOptionDict(
                value=CalibrationMode.HEATING_POWER_CALIBRATION, label="AI Time Based"
            ),
            selector.SelectOptionDict(
                value=CalibrationMode.NO_CALIBRATION, label="No Calibration"
            ),
        ],
        mode=selector.SelectSelectorMode.DROPDOWN,
    )
)


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    VERSION = 5
    CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL

    def __init__(self):
        """Initialize the config flow."""
        self.name = ""
        self.data = None
        self.model = None
        self.heater_entity_id = None
        self.trv_bundle = []
        self.integration = None
        self.i = 0

    @staticmethod
    @callback
    def async_get_options_flow(
        config_entry: config_entries.ConfigEntry,
    ) -> config_entries.OptionsFlow:
        """Get the options flow for this handler."""
        return OptionsFlowHandler(config_entry)
        """Get the options flow for this handler."""

    async def async_step_confirm(self, user_input=None, confirm_type=None):
        """Handle user-confirmation of discovered node."""
        errors = {}
        self.data[CONF_HEATER] = self.trv_bundle
        if user_input is not None:
            if self.data is not None:
                _LOGGER.debug("Confirm: %s", self.data[CONF_HEATER])
                await self.async_set_unique_id(self.data["name"])
                return self.async_create_entry(title=self.data["name"], data=self.data)
        if confirm_type is not None:
            errors["base"] = confirm_type
        _trvs = ",".join([x["trv"] for x in self.data[CONF_HEATER]])
        return self.async_show_form(
            step_id="confirm",
            errors=errors,
            description_placeholders={"name": self.data[CONF_NAME], "trv": _trvs},
        )

    async def async_step_advanced(self, user_input=None, _trv_config=None):
        """Handle options flow."""
        if user_input is not None:
            self.trv_bundle[self.i]["advanced"] = user_input
            self.trv_bundle[self.i]["adapter"] = None

            self.i += 1
            if len(self.trv_bundle) > self.i:
                return await self.async_step_advanced(None, self.trv_bundle[self.i])

            _has_off_mode = True
            for trv in self.trv_bundle:
                if HVACMode.OFF not in self.hass.states.get(
                    trv.get("trv")
                ).attributes.get("hvac_modes"):
                    _has_off_mode = False

            if _has_off_mode is False:
                return await self.async_step_confirm(None, "no_off_mode")
            return await self.async_step_confirm()

        user_input = user_input or {}
        homematic = False
        if _trv_config.get("integration").find("homematic") != -1:
            homematic = True

        fields = OrderedDict()

        _default_calibration = "target_temp_based"
        _adapter = _trv_config.get("adapter", None)
        if _adapter is not None:
            _info = await _adapter.get_info(self, _trv_config.get("trv"))

            if _info.get("support_offset", False):
                _default_calibration = "local_calibration_based"

        if _default_calibration == "local_calibration_based":
            fields[
                vol.Required(
                    CONF_CALIBRATION,
                    default=user_input.get(CONF_CALIBRATION, _default_calibration),
                )
            ] = CALIBRATION_TYPE_ALL_SELECTOR
        else:
            fields[
                vol.Required(
                    CONF_CALIBRATION,
                    default=user_input.get(CONF_CALIBRATION, _default_calibration),
                )
            ] = CALIBRATION_TYPE_SELECTOR

        fields[
            vol.Required(
                CONF_CALIBRATION_MODE,
                default=user_input.get(
                    CONF_CALIBRATION_MODE, CalibrationMode.HEATING_POWER_CALIBRATION
                ),
            )
        ] = CALIBRATION_MODE_SELECTOR

        fields[
            vol.Optional(
                CONF_PROTECT_OVERHEATING,
                default=user_input.get(CONF_PROTECT_OVERHEATING, False),
            )
        ] = bool

        fields[
            vol.Optional(
                CONF_NO_SYSTEM_MODE_OFF,
                default=user_input.get(CONF_NO_SYSTEM_MODE_OFF, False),
            )
        ] = bool

        fields[
            vol.Optional(
                CONF_HEAT_AUTO_SWAPPED,
                default=user_input.get(CONF_HEAT_AUTO_SWAPPED, False),
            )
        ] = bool

        if _info.get("support_valve", False):
            fields[
                vol.Optional(
                    CONF_VALVE_MAINTENANCE,
                    default=user_input.get(CONF_VALVE_MAINTENANCE, False),
                )
            ] = bool

        fields[
            vol.Optional(
                CONF_CHILD_LOCK, default=user_input.get(CONF_CHILD_LOCK, False)
            )
        ] = bool
        fields[
            vol.Optional(
                CONF_HOMATICIP, default=user_input.get(CONF_HOMATICIP, homematic)
            )
        ] = bool

        return self.async_show_form(
            step_id="advanced",
            data_schema=vol.Schema(fields),
            last_step=False,
            description_placeholders={"trv": _trv_config.get("trv")},
        )

    async def async_step_user(self, user_input=None):
        errors = {}

        if user_input is not None:
            if self.data is None:
                self.data = user_input
            self.heater_entity_id = self.data[CONF_HEATER]
            if self.data[CONF_NAME] == "":
                errors["base"] = "no_name"
            if CONF_SENSOR_WINDOW not in self.data:
                self.data[CONF_SENSOR_WINDOW] = None
            if CONF_HUMIDITY not in self.data:
                self.data[CONF_HUMIDITY] = None
            if CONF_OUTDOOR_SENSOR not in self.data:
                self.data[CONF_OUTDOOR_SENSOR] = None
            if CONF_WEATHER not in self.data:
                self.data[CONF_WEATHER] = None

            if CONF_WINDOW_TIMEOUT in self.data:
                self.data[CONF_WINDOW_TIMEOUT] = (
                    int(
                        cv.time_period_dict(
                            user_input.get(CONF_WINDOW_TIMEOUT, None)
                        ).total_seconds()
                    )
                    or 0
                )
            else:
                self.data[CONF_WINDOW_TIMEOUT] = 0

            if "base" not in errors:
                for trv in self.heater_entity_id:
                    _intigration = await get_trv_intigration(self, trv)
                    self.trv_bundle.append(
                        {
                            "trv": trv,
                            "integration": _intigration,
                            "model": await get_device_model(self, trv),
                            "adapter": load_adapter(self, _intigration, trv),
                        }
                    )
                self.data[CONF_MODEL] = "/".join([x["model"] for x in self.trv_bundle])
                return await self.async_step_advanced(None, self.trv_bundle[0])

        user_input = user_input or {}

        return self.async_show_form(
            step_id="user",
            data_schema=vol.Schema(
                {
                    vol.Optional(CONF_NAME, default=user_input.get(CONF_NAME, "")): str,
                    vol.Required(CONF_HEATER): selector.EntitySelector(
                        selector.EntitySelectorConfig(domain="climate", multiple=True)
                    ),
                    vol.Required(CONF_SENSOR): selector.EntitySelector(
                        selector.EntitySelectorConfig(
                            domain=["sensor", "number", "input_number"],
                            device_class="temperature",
                            multiple=False,
                        )
                    ),
                    vol.Optional(CONF_HUMIDITY): selector.EntitySelector(
                        selector.EntitySelectorConfig(
                            domain=["sensor", "number", "input_number"],
                            device_class="humidity",
                            multiple=False,
                        )
                    ),
                    vol.Optional(CONF_OUTDOOR_SENSOR): selector.EntitySelector(
                        selector.EntitySelectorConfig(
                            domain=["sensor", "input_number", "number"],
                            device_class="temperature",
                            multiple=False,
                        )
                    ),
                    vol.Optional(CONF_SENSOR_WINDOW): selector.EntitySelector(
                        selector.EntitySelectorConfig(
                            domain=[
                                "group",
                                "sensor",
                                "input_boolean",
                                "binary_sensor",
                            ],
                            multiple=False,
                        )
                    ),
                    vol.Optional(CONF_WEATHER): selector.EntitySelector(
                        selector.EntitySelectorConfig(domain="weather", multiple=False)
                    ),
                    vol.Optional(CONF_WINDOW_TIMEOUT): selector.DurationSelector(),
                    vol.Optional(
                        CONF_OFF_TEMPERATURE,
                        default=user_input.get(CONF_OFF_TEMPERATURE, 20),
                    ): int,
                    vol.Optional(
                        CONF_TOLERANCE,
                        default=user_input.get(CONF_TOLERANCE, 0.0),
                    ): float,
                }
            ),
            errors=errors,
            last_step=False,
        )


class OptionsFlowHandler(config_entries.OptionsFlow):
    """Handle a option flow for a config entry."""

    def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
        """Initialize options flow."""
        self.config_entry = config_entry
        self.options = dict(config_entry.options)
        self.i = 0
        self.trv_bundle = []
        self.name = ""
        self._last_step = False
        self.updated_config = {}

    async def async_step_init(self, user_input=None):
        """Manage the options."""
        return await self.async_step_user()

    async def async_step_advanced(
        self, user_input=None, _trv_config=None, _update_config=None
    ):
        """Manage the advanced options."""
        if user_input is not None:
            self.trv_bundle[self.i]["advanced"] = user_input
            self.trv_bundle[self.i]["adapter"] = None

            self.i += 1
            if len(self.trv_bundle) - 1 >= self.i:
                self._last_step = True

            if len(self.trv_bundle) > self.i:
                return await self.async_step_advanced(
                    None, self.trv_bundle[self.i], _update_config
                )

            self.updated_config[CONF_HEATER] = self.trv_bundle
            _LOGGER.debug("Updated config: %s", self.updated_config)
            self.hass.config_entries.async_update_entry(
                self.config_entry, data=self.updated_config
            )
            return self.async_create_entry(
                title=self.updated_config["name"], data=self.updated_config
            )

        user_input = user_input or {}
        homematic = False
        if _trv_config.get("integration").find("homematic") != -1:
            homematic = True

        fields = OrderedDict()

        _default_calibration = "target_temp_based"
        self.name = user_input.get(CONF_NAME, "-")

        _adapter = load_adapter(
            self, _trv_config.get("integration"), _trv_config.get("trv")
        )
        if _adapter is not None:
            _info = await _adapter.get_info(self, _trv_config.get("trv"))

            if _info.get("support_offset", False):
                _default_calibration = "local_calibration_based"

        if _default_calibration == "local_calibration_based":
            fields[
                vol.Required(
                    CONF_CALIBRATION,
                    default=user_input.get(
                        CONF_CALIBRATION,
                        _trv_config["advanced"].get(
                            CONF_CALIBRATION, _default_calibration
                        ),
                    ),
                )
            ] = CALIBRATION_TYPE_ALL_SELECTOR
        else:
            fields[
                vol.Required(
                    CONF_CALIBRATION,
                    default=user_input.get(
                        CONF_CALIBRATION,
                        _trv_config["advanced"].get(
                            CONF_CALIBRATION, _default_calibration
                        ),
                    ),
                )
            ] = CALIBRATION_TYPE_SELECTOR

        fields[
            vol.Required(
                CONF_CALIBRATION_MODE,
                default=_trv_config["advanced"].get(
                    CONF_CALIBRATION_MODE, CalibrationMode.HEATING_POWER_CALIBRATION
                ),
            )
        ] = CALIBRATION_MODE_SELECTOR

        fields[
            vol.Optional(
                CONF_PROTECT_OVERHEATING,
                default=_trv_config["advanced"].get(CONF_PROTECT_OVERHEATING, False),
            )
        ] = bool

        fields[
            vol.Optional(
                CONF_NO_SYSTEM_MODE_OFF,
                default=_trv_config["advanced"].get(CONF_NO_SYSTEM_MODE_OFF, False),
            )
        ] = bool

        has_auto = False
        trv = self.hass.states.get(_trv_config.get("trv"))
        if HVACMode.AUTO in trv.attributes.get("hvac_modes"):
            has_auto = True

        fields[
            vol.Optional(
                CONF_HEAT_AUTO_SWAPPED,
                default=_trv_config["advanced"].get(CONF_HEAT_AUTO_SWAPPED, has_auto),
            )
        ] = bool

        if _info.get("support_valve", False):
            fields[
                vol.Optional(
                    CONF_VALVE_MAINTENANCE,
                    default=_trv_config["advanced"].get(CONF_VALVE_MAINTENANCE, False),
                )
            ] = bool

        fields[
            vol.Optional(
                CONF_CHILD_LOCK,
                default=_trv_config["advanced"].get(CONF_CHILD_LOCK, False),
            )
        ] = bool
        fields[
            vol.Optional(
                CONF_HOMATICIP,
                default=_trv_config["advanced"].get(CONF_HOMATICIP, homematic),
            )
        ] = bool

        return self.async_show_form(
            step_id="advanced",
            data_schema=vol.Schema(fields),
            last_step=self._last_step,
            description_placeholders={"trv": _trv_config.get("trv")},
        )

    async def async_step_user(self, user_input=None):
        if user_input is not None:
            current_config = self.config_entry.data
            self.updated_config = dict(current_config)
            self.updated_config[CONF_SENSOR] = user_input.get(CONF_SENSOR, None)
            self.updated_config[CONF_SENSOR_WINDOW] = user_input.get(
                CONF_SENSOR_WINDOW, None
            )
            self.updated_config[CONF_HUMIDITY] = user_input.get(CONF_HUMIDITY, None)
            self.updated_config[CONF_OUTDOOR_SENSOR] = user_input.get(
                CONF_OUTDOOR_SENSOR, None
            )
            self.updated_config[CONF_WEATHER] = user_input.get(CONF_WEATHER, None)

            if CONF_WINDOW_TIMEOUT in self.updated_config:
                self.updated_config[CONF_WINDOW_TIMEOUT] = (
                    int(
                        cv.time_period_dict(
                            user_input.get(CONF_WINDOW_TIMEOUT, None)
                        ).total_seconds()
                    )
                    or 0
                )
            else:
                self.updated_config[CONF_WINDOW_TIMEOUT] = 0

            self.updated_config[CONF_OFF_TEMPERATURE] = user_input.get(
                CONF_OFF_TEMPERATURE
            )

            self.updated_config[CONF_TOLERANCE] = user_input.get(CONF_TOLERANCE, 0.0)

            for trv in self.updated_config[CONF_HEATER]:
                trv["adapter"] = None
                self.trv_bundle.append(trv)

            return await self.async_step_advanced(
                None, self.trv_bundle[0], self.updated_config
            )

        fields = OrderedDict()

        fields[
            vol.Optional(
                CONF_SENSOR,
                description={
                    "suggested_value": self.config_entry.data.get(CONF_SENSOR, "")
                },
            )
        ] = selector.EntitySelector(
            selector.EntitySelectorConfig(
                domain=["sensor", "number", "input_number"],
                device_class="temperature",
                multiple=False,
            )
        )

        fields[
            vol.Optional(
                CONF_HUMIDITY,
                description={
                    "suggested_value": self.config_entry.data.get(CONF_HUMIDITY, "")
                },
            )
        ] = selector.EntitySelector(
            selector.EntitySelectorConfig(
                domain=["sensor", "number", "input_number"],
                device_class="humidity",
                multiple=False,
            )
        )

        fields[
            vol.Optional(
                CONF_SENSOR_WINDOW,
                description={
                    "suggested_value": self.config_entry.data.get(
                        CONF_SENSOR_WINDOW, ""
                    )
                },
            )
        ] = selector.EntitySelector(
            selector.EntitySelectorConfig(
                domain=["group", "sensor", "input_boolean", "binary_sensor"],
                multiple=False,
            )
        )

        fields[
            vol.Optional(
                CONF_OUTDOOR_SENSOR,
                description={
                    "suggested_value": self.config_entry.data.get(
                        CONF_OUTDOOR_SENSOR, ""
                    )
                },
            )
        ] = selector.EntitySelector(
            selector.EntitySelectorConfig(
                domain=["sensor", "input_number", "number"],
                device_class="temperature",
                multiple=False,
            )
        )

        fields[
            vol.Optional(
                CONF_WEATHER,
                description={
                    "suggested_value": self.config_entry.data.get(CONF_WEATHER, "")
                },
            )
        ] = selector.EntitySelector(
            selector.EntitySelectorConfig(domain="weather", multiple=False)
        )

        _timeout = self.config_entry.data.get(CONF_WINDOW_TIMEOUT, 0)
        _timeout = str(cv.time_period_seconds(_timeout))
        _timeout = {
            "hours": int(_timeout.split(":", maxsplit=1)[0]),
            "minutes": int(_timeout.split(":")[1]),
            "seconds": int(_timeout.split(":")[2]),
        }
        fields[
            vol.Optional(
                CONF_WINDOW_TIMEOUT,
                default=_timeout,
                description={"suggested_value": _timeout},
            )
        ] = selector.DurationSelector()

        fields[
            vol.Optional(
                CONF_OFF_TEMPERATURE,
                default=self.config_entry.data.get(CONF_OFF_TEMPERATURE, 5),
            )
        ] = int

        fields[
            vol.Optional(
                CONF_TOLERANCE,
                default=self.config_entry.data.get(CONF_TOLERANCE, 0.0),
            )
        ] = float

        return self.async_show_form(
            step_id="user", data_schema=vol.Schema(fields), last_step=False
        )
