"""
Helpers for Zigbee Home Automation.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/zha/
"""
import asyncio
import logging

from .const import (
    DEFAULT_BAUDRATE, REPORT_CONFIG_MAX_INT, REPORT_CONFIG_MIN_INT,
    REPORT_CONFIG_RPT_CHANGE, RadioType)

_LOGGER = logging.getLogger(__name__)


async def safe_read(cluster, attributes, allow_cache=True, only_cache=False,
                    manufacturer=None):
    """Swallow all exceptions from network read.

    If we throw during initialization, setup fails. Rather have an entity that
    exists, but is in a maybe wrong state, than no entity. This method should
    probably only be used during initialization.
    """
    try:
        result, _ = await cluster.read_attributes(
            attributes,
            allow_cache=allow_cache,
            only_cache=only_cache,
            manufacturer=manufacturer
        )
        return result
    except Exception:  # pylint: disable=broad-except
        return {}


async def bind_cluster(entity_id, cluster):
    """Bind a zigbee cluster.

    This also swallows DeliveryError exceptions that are thrown when devices
    are unreachable.
    """
    from zigpy.exceptions import DeliveryError

    cluster_name = cluster.ep_attribute
    try:
        res = await cluster.bind()
        _LOGGER.debug(
            "%s: bound  '%s' cluster: %s", entity_id, cluster_name, res[0]
        )
    except DeliveryError as ex:
        _LOGGER.debug(
            "%s: Failed to bind '%s' cluster: %s",
            entity_id, cluster_name, str(ex)
        )


async def configure_reporting(entity_id, cluster, attr, skip_bind=False,
                              min_report=REPORT_CONFIG_MIN_INT,
                              max_report=REPORT_CONFIG_MAX_INT,
                              reportable_change=REPORT_CONFIG_RPT_CHANGE,
                              manufacturer=None):
    """Configure attribute reporting for a cluster.

    This also swallows DeliveryError exceptions that are thrown when devices
    are unreachable.
    """
    from zigpy.exceptions import DeliveryError

    attr_name = cluster.attributes.get(attr, [attr])[0]
    cluster_name = cluster.ep_attribute
    kwargs = {}
    if manufacturer:
        kwargs['manufacturer'] = manufacturer
    try:
        res = await cluster.configure_reporting(attr, min_report,
                                                max_report, reportable_change,
                                                **kwargs)
        _LOGGER.debug(
            "%s: reporting '%s' attr on '%s' cluster: %d/%d/%d: Result: '%s'",
            entity_id, attr_name, cluster_name, min_report, max_report,
            reportable_change, res
        )
    except DeliveryError as ex:
        _LOGGER.debug(
            "%s: failed to set reporting for '%s' attr on '%s' cluster: %s",
            entity_id, attr_name, cluster_name, str(ex)
        )


async def bind_configure_reporting(entity_id, cluster, attr, skip_bind=False,
                                   min_report=REPORT_CONFIG_MIN_INT,
                                   max_report=REPORT_CONFIG_MAX_INT,
                                   reportable_change=REPORT_CONFIG_RPT_CHANGE,
                                   manufacturer=None):
    """Bind and configure zigbee attribute reporting for a cluster.

    This also swallows DeliveryError exceptions that are thrown when devices
    are unreachable.
    """
    if not skip_bind:
        await bind_cluster(entity_id, cluster)

    await configure_reporting(entity_id, cluster, attr, skip_bind=False,
                              min_report=REPORT_CONFIG_MIN_INT,
                              max_report=REPORT_CONFIG_MAX_INT,
                              reportable_change=REPORT_CONFIG_RPT_CHANGE,
                              manufacturer=None)


async def check_zigpy_connection(usb_path, radio_type, database_path):
    """Test zigpy radio connection."""
    if radio_type == RadioType.ezsp.name:
        import bellows.ezsp
        from bellows.zigbee.application import ControllerApplication
        radio = bellows.ezsp.EZSP()
    elif radio_type == RadioType.xbee.name:
        import zigpy_xbee.api
        from zigpy_xbee.zigbee.application import ControllerApplication
        radio = zigpy_xbee.api.XBee()
    elif radio_type == RadioType.deconz.name:
        import zigpy_deconz.api
        from zigpy_deconz.zigbee.application import ControllerApplication
        radio = zigpy_deconz.api.Deconz()
    try:
        await radio.connect(usb_path, DEFAULT_BAUDRATE)
        controller = ControllerApplication(radio, database_path)
        await asyncio.wait_for(controller.startup(auto_form=True), timeout=30)
        radio.close()
    except Exception:  # pylint: disable=broad-except
        return False
    return True


def convert_ieee(ieee_str):
    """Convert given ieee string to EUI64."""
    from zigpy.types import EUI64, uint8_t
    return EUI64([uint8_t(p, base=16) for p in ieee_str.split(':')])