"""Support for Supla devices."""
from __future__ import annotations

from datetime import timedelta
import logging

import async_timeout
from asyncpysupla import SuplaAPI
import voluptuous as vol

from homeassistant.const import CONF_ACCESS_TOKEN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import (
    CoordinatorEntity,
    DataUpdateCoordinator,
)

_LOGGER = logging.getLogger(__name__)

DOMAIN = "supla"
CONF_SERVER = "server"
CONF_SERVERS = "servers"

SCAN_INTERVAL = timedelta(seconds=10)

SUPLA_FUNCTION_HA_CMP_MAP = {
    "CONTROLLINGTHEROLLERSHUTTER": Platform.COVER,
    "CONTROLLINGTHEGATE": Platform.COVER,
    "LIGHTSWITCH": Platform.SWITCH,
}
SUPLA_FUNCTION_NONE = "NONE"
SUPLA_SERVERS = "supla_servers"
SUPLA_COORDINATORS = "supla_coordinators"

SERVER_CONFIG = vol.Schema(
    {
        vol.Required(CONF_SERVER): cv.string,
        vol.Required(CONF_ACCESS_TOKEN): cv.string,
    }
)

CONFIG_SCHEMA = vol.Schema(
    {
        DOMAIN: vol.Schema(
            {vol.Required(CONF_SERVERS): vol.All(cv.ensure_list, [SERVER_CONFIG])}
        )
    },
    extra=vol.ALLOW_EXTRA,
)


async def async_setup(hass: HomeAssistant, base_config: ConfigType) -> bool:
    """Set up the Supla component."""

    server_confs = base_config[DOMAIN][CONF_SERVERS]

    hass.data[DOMAIN] = {SUPLA_SERVERS: {}, SUPLA_COORDINATORS: {}}

    session = async_get_clientsession(hass)

    for server_conf in server_confs:

        server_address = server_conf[CONF_SERVER]

        server = SuplaAPI(server_address, server_conf[CONF_ACCESS_TOKEN], session)

        # Test connection
        try:
            srv_info = await server.get_server_info()
            if srv_info.get("authenticated"):
                hass.data[DOMAIN][SUPLA_SERVERS][server_conf[CONF_SERVER]] = server

            else:
                _LOGGER.error(
                    "Server: %s not configured. API call returned: %s",
                    server_address,
                    srv_info,
                )
                return False
        except OSError:
            _LOGGER.exception(
                "Server: %s not configured. Error on Supla API access: ", server_address
            )
            return False

    await discover_devices(hass, base_config)

    return True


async def discover_devices(hass, hass_config):
    """
    Run periodically to discover new devices.

    Currently it is only run at startup.
    """
    component_configs: dict[Platform, dict[str, dict]] = {}

    for server_name, server in hass.data[DOMAIN][SUPLA_SERVERS].items():

        async def _fetch_channels():
            async with async_timeout.timeout(SCAN_INTERVAL.total_seconds()):
                channels = {
                    channel["id"]: channel
                    for channel in await server.get_channels(  # pylint: disable=cell-var-from-loop
                        include=["iodevice", "state", "connected"]
                    )
                }
                return channels

        coordinator = DataUpdateCoordinator(
            hass,
            _LOGGER,
            name=f"{DOMAIN}-{server_name}",
            update_method=_fetch_channels,
            update_interval=SCAN_INTERVAL,
        )

        await coordinator.async_refresh()

        hass.data[DOMAIN][SUPLA_COORDINATORS][server_name] = coordinator

        for channel_id, channel in coordinator.data.items():
            channel_function = channel["function"]["name"]

            if channel_function == SUPLA_FUNCTION_NONE:
                _LOGGER.debug(
                    "Ignored function: %s, channel ID: %s",
                    channel_function,
                    channel["id"],
                )
                continue

            component_name = SUPLA_FUNCTION_HA_CMP_MAP.get(channel_function)

            if component_name is None:
                _LOGGER.warning(
                    "Unsupported function: %s, channel ID: %s",
                    channel_function,
                    channel["id"],
                )
                continue

            channel["server_name"] = server_name
            component_config = component_configs.setdefault(component_name, {})
            component_config[f"{server_name}_{channel_id}"] = {
                "channel_id": channel_id,
                "server_name": server_name,
                "function_name": channel["function"]["name"],
            }

    # Load discovered devices
    for component_name, config in component_configs.items():
        await async_load_platform(hass, component_name, DOMAIN, config, hass_config)


class SuplaChannel(CoordinatorEntity):
    """Base class of a Supla Channel (an equivalent of HA's Entity)."""

    def __init__(self, config, server, coordinator):
        """Init from config, hookup[ server and coordinator."""
        super().__init__(coordinator)
        self.server_name = config["server_name"]
        self.channel_id = config["channel_id"]
        self.server = server

    @property
    def channel_data(self):
        """Return channel data taken from coordinator."""
        return self.coordinator.data.get(self.channel_id)

    @property
    def unique_id(self) -> str:
        """Return a unique ID."""
        return "supla-{}-{}".format(
            self.channel_data["iodevice"]["gUIDString"].lower(),
            self.channel_data["channelNumber"],
        )

    @property
    def name(self) -> str | None:
        """Return the name of the device."""
        return self.channel_data["caption"]

    @property
    def available(self) -> bool:
        """Return True if entity is available."""
        if self.channel_data is None:
            return False
        if (state := self.channel_data.get("state")) is None:
            return False
        return state.get("connected")

    async def async_action(self, action, **add_pars):
        """
        Run server action.

        Actions are currently hardcoded in components.
        Supla's API enables autodiscovery
        """
        _LOGGER.debug(
            "Executing action %s on channel %d, params: %s",
            action,
            self.channel_data["id"],
            add_pars,
        )
        await self.server.execute_action(self.channel_data["id"], action, **add_pars)

        # Update state
        await self.coordinator.async_request_refresh()
