"""Support for HomematicIP Cloud devices."""
import logging
from pathlib import Path
from typing import Optional

from homematicip.aio.device import AsyncSwitchMeasuring
from homematicip.aio.group import AsyncHeatingGroup
from homematicip.aio.home import AsyncHome
from homematicip.base.helpers import handle_config
import voluptuous as vol

from homeassistant.const import ATTR_ENTITY_ID
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import comp_entity_ids
from homeassistant.helpers.service import (
    async_register_admin_service,
    verify_domain_control,
)
from homeassistant.helpers.typing import HomeAssistantType, ServiceCallType

from .const import DOMAIN as HMIPC_DOMAIN

_LOGGER = logging.getLogger(__name__)

ATTR_ACCESSPOINT_ID = "accesspoint_id"
ATTR_ANONYMIZE = "anonymize"
ATTR_CLIMATE_PROFILE_INDEX = "climate_profile_index"
ATTR_CONFIG_OUTPUT_FILE_PREFIX = "config_output_file_prefix"
ATTR_CONFIG_OUTPUT_PATH = "config_output_path"
ATTR_DURATION = "duration"
ATTR_ENDTIME = "endtime"
ATTR_TEMPERATURE = "temperature"

DEFAULT_CONFIG_FILE_PREFIX = "hmip-config"

SERVICE_ACTIVATE_ECO_MODE_WITH_DURATION = "activate_eco_mode_with_duration"
SERVICE_ACTIVATE_ECO_MODE_WITH_PERIOD = "activate_eco_mode_with_period"
SERVICE_ACTIVATE_VACATION = "activate_vacation"
SERVICE_DEACTIVATE_ECO_MODE = "deactivate_eco_mode"
SERVICE_DEACTIVATE_VACATION = "deactivate_vacation"
SERVICE_DUMP_HAP_CONFIG = "dump_hap_config"
SERVICE_RESET_ENERGY_COUNTER = "reset_energy_counter"
SERVICE_SET_ACTIVE_CLIMATE_PROFILE = "set_active_climate_profile"

HMIPC_SERVICES = [
    SERVICE_ACTIVATE_ECO_MODE_WITH_DURATION,
    SERVICE_ACTIVATE_ECO_MODE_WITH_PERIOD,
    SERVICE_ACTIVATE_VACATION,
    SERVICE_DEACTIVATE_ECO_MODE,
    SERVICE_DEACTIVATE_VACATION,
    SERVICE_DUMP_HAP_CONFIG,
    SERVICE_RESET_ENERGY_COUNTER,
    SERVICE_SET_ACTIVE_CLIMATE_PROFILE,
]

SCHEMA_ACTIVATE_ECO_MODE_WITH_DURATION = vol.Schema(
    {
        vol.Required(ATTR_DURATION): cv.positive_int,
        vol.Optional(ATTR_ACCESSPOINT_ID): vol.All(str, vol.Length(min=24, max=24)),
    }
)

SCHEMA_ACTIVATE_ECO_MODE_WITH_PERIOD = vol.Schema(
    {
        vol.Required(ATTR_ENDTIME): cv.datetime,
        vol.Optional(ATTR_ACCESSPOINT_ID): vol.All(str, vol.Length(min=24, max=24)),
    }
)

SCHEMA_ACTIVATE_VACATION = vol.Schema(
    {
        vol.Required(ATTR_ENDTIME): cv.datetime,
        vol.Required(ATTR_TEMPERATURE, default=18.0): vol.All(
            vol.Coerce(float), vol.Range(min=0, max=55)
        ),
        vol.Optional(ATTR_ACCESSPOINT_ID): vol.All(str, vol.Length(min=24, max=24)),
    }
)

SCHEMA_DEACTIVATE_ECO_MODE = vol.Schema(
    {vol.Optional(ATTR_ACCESSPOINT_ID): vol.All(str, vol.Length(min=24, max=24))}
)

SCHEMA_DEACTIVATE_VACATION = vol.Schema(
    {vol.Optional(ATTR_ACCESSPOINT_ID): vol.All(str, vol.Length(min=24, max=24))}
)

SCHEMA_SET_ACTIVE_CLIMATE_PROFILE = vol.Schema(
    {
        vol.Required(ATTR_ENTITY_ID): comp_entity_ids,
        vol.Required(ATTR_CLIMATE_PROFILE_INDEX): cv.positive_int,
    }
)

SCHEMA_DUMP_HAP_CONFIG = vol.Schema(
    {
        vol.Optional(ATTR_CONFIG_OUTPUT_PATH): cv.string,
        vol.Optional(
            ATTR_CONFIG_OUTPUT_FILE_PREFIX, default=DEFAULT_CONFIG_FILE_PREFIX
        ): cv.string,
        vol.Optional(ATTR_ANONYMIZE, default=True): cv.boolean,
    }
)

SCHEMA_RESET_ENERGY_COUNTER = vol.Schema(
    {vol.Required(ATTR_ENTITY_ID): comp_entity_ids}
)


async def async_setup_services(hass: HomeAssistantType) -> None:
    """Set up the HomematicIP Cloud services."""

    if hass.services.async_services().get(HMIPC_DOMAIN):
        return

    @verify_domain_control(hass, HMIPC_DOMAIN)
    async def async_call_hmipc_service(service: ServiceCallType):
        """Call correct HomematicIP Cloud service."""
        service_name = service.service

        if service_name == SERVICE_ACTIVATE_ECO_MODE_WITH_DURATION:
            await _async_activate_eco_mode_with_duration(hass, service)
        elif service_name == SERVICE_ACTIVATE_ECO_MODE_WITH_PERIOD:
            await _async_activate_eco_mode_with_period(hass, service)
        elif service_name == SERVICE_ACTIVATE_VACATION:
            await _async_activate_vacation(hass, service)
        elif service_name == SERVICE_DEACTIVATE_ECO_MODE:
            await _async_deactivate_eco_mode(hass, service)
        elif service_name == SERVICE_DEACTIVATE_VACATION:
            await _async_deactivate_vacation(hass, service)
        elif service_name == SERVICE_DUMP_HAP_CONFIG:
            await _async_dump_hap_config(hass, service)
        elif service_name == SERVICE_RESET_ENERGY_COUNTER:
            await _async_reset_energy_counter(hass, service)
        elif service_name == SERVICE_SET_ACTIVE_CLIMATE_PROFILE:
            await _set_active_climate_profile(hass, service)

    hass.services.async_register(
        domain=HMIPC_DOMAIN,
        service=SERVICE_ACTIVATE_ECO_MODE_WITH_DURATION,
        service_func=async_call_hmipc_service,
        schema=SCHEMA_ACTIVATE_ECO_MODE_WITH_DURATION,
    )

    hass.services.async_register(
        domain=HMIPC_DOMAIN,
        service=SERVICE_ACTIVATE_ECO_MODE_WITH_PERIOD,
        service_func=async_call_hmipc_service,
        schema=SCHEMA_ACTIVATE_ECO_MODE_WITH_PERIOD,
    )

    hass.services.async_register(
        domain=HMIPC_DOMAIN,
        service=SERVICE_ACTIVATE_VACATION,
        service_func=async_call_hmipc_service,
        schema=SCHEMA_ACTIVATE_VACATION,
    )

    hass.services.async_register(
        domain=HMIPC_DOMAIN,
        service=SERVICE_DEACTIVATE_ECO_MODE,
        service_func=async_call_hmipc_service,
        schema=SCHEMA_DEACTIVATE_ECO_MODE,
    )

    hass.services.async_register(
        domain=HMIPC_DOMAIN,
        service=SERVICE_DEACTIVATE_VACATION,
        service_func=async_call_hmipc_service,
        schema=SCHEMA_DEACTIVATE_VACATION,
    )

    hass.services.async_register(
        domain=HMIPC_DOMAIN,
        service=SERVICE_SET_ACTIVE_CLIMATE_PROFILE,
        service_func=async_call_hmipc_service,
        schema=SCHEMA_SET_ACTIVE_CLIMATE_PROFILE,
    )

    async_register_admin_service(
        hass=hass,
        domain=HMIPC_DOMAIN,
        service=SERVICE_DUMP_HAP_CONFIG,
        service_func=async_call_hmipc_service,
        schema=SCHEMA_DUMP_HAP_CONFIG,
    )

    async_register_admin_service(
        hass=hass,
        domain=HMIPC_DOMAIN,
        service=SERVICE_RESET_ENERGY_COUNTER,
        service_func=async_call_hmipc_service,
        schema=SCHEMA_RESET_ENERGY_COUNTER,
    )


async def async_unload_services(hass: HomeAssistantType):
    """Unload HomematicIP Cloud services."""
    if hass.data[HMIPC_DOMAIN]:
        return

    for hmipc_service in HMIPC_SERVICES:
        hass.services.async_remove(domain=HMIPC_DOMAIN, service=hmipc_service)


async def _async_activate_eco_mode_with_duration(
    hass: HomeAssistantType, service: ServiceCallType
) -> None:
    """Service to activate eco mode with duration."""
    duration = service.data[ATTR_DURATION]
    hapid = service.data.get(ATTR_ACCESSPOINT_ID)

    if hapid:
        home = _get_home(hass, hapid)
        if home:
            await home.activate_absence_with_duration(duration)
    else:
        for hap in hass.data[HMIPC_DOMAIN].values():
            await hap.home.activate_absence_with_duration(duration)


async def _async_activate_eco_mode_with_period(
    hass: HomeAssistantType, service: ServiceCallType
) -> None:
    """Service to activate eco mode with period."""
    endtime = service.data[ATTR_ENDTIME]
    hapid = service.data.get(ATTR_ACCESSPOINT_ID)

    if hapid:
        home = _get_home(hass, hapid)
        if home:
            await home.activate_absence_with_period(endtime)
    else:
        for hap in hass.data[HMIPC_DOMAIN].values():
            await hap.home.activate_absence_with_period(endtime)


async def _async_activate_vacation(
    hass: HomeAssistantType, service: ServiceCallType
) -> None:
    """Service to activate vacation."""
    endtime = service.data[ATTR_ENDTIME]
    temperature = service.data[ATTR_TEMPERATURE]
    hapid = service.data.get(ATTR_ACCESSPOINT_ID)

    if hapid:
        home = _get_home(hass, hapid)
        if home:
            await home.activate_vacation(endtime, temperature)
    else:
        for hap in hass.data[HMIPC_DOMAIN].values():
            await hap.home.activate_vacation(endtime, temperature)


async def _async_deactivate_eco_mode(
    hass: HomeAssistantType, service: ServiceCallType
) -> None:
    """Service to deactivate eco mode."""
    hapid = service.data.get(ATTR_ACCESSPOINT_ID)

    if hapid:
        home = _get_home(hass, hapid)
        if home:
            await home.deactivate_absence()
    else:
        for hap in hass.data[HMIPC_DOMAIN].values():
            await hap.home.deactivate_absence()


async def _async_deactivate_vacation(
    hass: HomeAssistantType, service: ServiceCallType
) -> None:
    """Service to deactivate vacation."""
    hapid = service.data.get(ATTR_ACCESSPOINT_ID)

    if hapid:
        home = _get_home(hass, hapid)
        if home:
            await home.deactivate_vacation()
    else:
        for hap in hass.data[HMIPC_DOMAIN].values():
            await hap.home.deactivate_vacation()


async def _set_active_climate_profile(
    hass: HomeAssistantType, service: ServiceCallType
) -> None:
    """Service to set the active climate profile."""
    entity_id_list = service.data[ATTR_ENTITY_ID]
    climate_profile_index = service.data[ATTR_CLIMATE_PROFILE_INDEX] - 1

    for hap in hass.data[HMIPC_DOMAIN].values():
        if entity_id_list != "all":
            for entity_id in entity_id_list:
                group = hap.hmip_device_by_entity_id.get(entity_id)
                if group and isinstance(group, AsyncHeatingGroup):
                    await group.set_active_profile(climate_profile_index)
        else:
            for group in hap.home.groups:
                if isinstance(group, AsyncHeatingGroup):
                    await group.set_active_profile(climate_profile_index)


async def _async_dump_hap_config(
    hass: HomeAssistantType, service: ServiceCallType
) -> None:
    """Service to dump the configuration of a Homematic IP Access Point."""
    config_path = service.data.get(ATTR_CONFIG_OUTPUT_PATH) or hass.config.config_dir
    config_file_prefix = service.data[ATTR_CONFIG_OUTPUT_FILE_PREFIX]
    anonymize = service.data[ATTR_ANONYMIZE]

    for hap in hass.data[HMIPC_DOMAIN].values():
        hap_sgtin = hap.config_entry.unique_id

        if anonymize:
            hap_sgtin = hap_sgtin[-4:]

        file_name = f"{config_file_prefix}_{hap_sgtin}.json"
        path = Path(config_path)
        config_file = path / file_name

        json_state = await hap.home.download_configuration()
        json_state = handle_config(json_state, anonymize)

        config_file.write_text(json_state, encoding="utf8")


async def _async_reset_energy_counter(
    hass: HomeAssistantType, service: ServiceCallType
):
    """Service to reset the energy counter."""
    entity_id_list = service.data[ATTR_ENTITY_ID]

    for hap in hass.data[HMIPC_DOMAIN].values():
        if entity_id_list != "all":
            for entity_id in entity_id_list:
                device = hap.hmip_device_by_entity_id.get(entity_id)
                if device and isinstance(device, AsyncSwitchMeasuring):
                    await device.reset_energy_counter()
        else:
            for device in hap.home.devices:
                if isinstance(device, AsyncSwitchMeasuring):
                    await device.reset_energy_counter()


def _get_home(hass: HomeAssistantType, hapid: str) -> Optional[AsyncHome]:
    """Return a HmIP home."""
    hap = hass.data[HMIPC_DOMAIN].get(hapid)
    if hap:
        return hap.home

    _LOGGER.info("No matching access point found for access point id %s", hapid)
    return None
