"""Python module for controlling Insteon devices."""
import asyncio
import logging

from pubsub import pub

from .address import Address
from .handlers.from_device.x10_received import X10Received
from .listener_exception_handler import ListenerExceptionHandler
from .managers.device_link_manager import DeviceLinkManager
from .managers.device_manager import DeviceManager
from .managers.link_manager.default_links import async_add_default_links

# pylint: disable=unused-import
from .managers.x10_manager import async_x10_all_lights_off  # noqa: F401
from .managers.x10_manager import async_x10_all_lights_on  # noqa: F401
from .managers.x10_manager import async_x10_all_units_off  # noqa: F401
from .protocol import async_modem_connect
from .topics import ADD_DEFAULT_LINKS
from .utils import subscribe_topic

_LOGGER_TOPICS = logging.getLogger("pyinsteon.topics")
X10_RECEIVED_HANDLER = X10Received()

devices = DeviceManager()
link_manager = DeviceLinkManager(devices)
subscribe_topic(async_add_default_links, ADD_DEFAULT_LINKS)


async def async_connect(
    device=None,
    host=None,
    port=None,
    username=None,
    password=None,
    hub_version=2,
    **kwargs,
):
    """Connect to the Insteon Modem.

    Parameters:
        device: PLM serial / USB device name
        host: Hub hostname or IP address
        port: Hub TCP port
        username: Hub v2 username
        password: Hub v2 password
        hub_version: Hub version (default=2)

    Returns an Insteon Modem (PLM or Hub).

    """
    try:
        modem = await async_modem_connect(
            device=device,
            host=host,
            port=port,
            username=username,
            password=password,
            hub_version=hub_version,
            **kwargs,
        )
    except ConnectionError as err:
        raise ConnectionError from err
    devices.modem = modem
    devices.id_manager.start()
    await devices.modem.async_get_configuration()
    return devices


async def async_close():
    """Close the connection and stop all tasks."""
    await devices.modem.async_close()
    for addr in devices:
        if devices[addr].is_battery:
            devices[addr].close()
    devices.id_manager.close()
    await asyncio.sleep(0.1)


def set_exception_handler():
    """Set the pubsub exception handler.

    This should only be run for debugging purposes.
    """
    pub.setListenerExcHandler(ListenerExceptionHandler())


def _get_device_in_topic(topic: pub.Topic, **kwargs):
    """Return a list of devices referenced in a topic."""
    addresses = []
    prefix = ["ack", "nak", "handler"]
    topic_tuple = topic.getNameTuple()
    try:
        if topic_tuple[0] == "send":
            pass
        elif topic_tuple[0] in prefix:
            addresses.append(Address(topic_tuple[1]))
        else:
            addresses.append(Address(topic_tuple[0]))
    except ValueError:
        pass

    for key, value in kwargs.items():
        if key in ["address", "target"]:
            try:
                addr = Address(value)
            except ValueError:
                continue
            else:
                if addr not in addresses and not str(addr).startswith("000"):
                    addresses.append(addr)

    return addresses


def _log_all_topics(topic=pub.AUTO_TOPIC, **kwargs):
    """Log all topics from pyinsteon."""
    _LOGGER_TOPICS.debug("Topic: %s data: %s", topic.name, kwargs)
    if _LOGGER_TOPICS.level > logging.DEBUG:
        addresses = _get_device_in_topic(topic, **kwargs)
        for addr in addresses:
            logger = logging.getLogger(f"pyinsteon.{addr.id}")
            logger.debug("Topic: %s data: %s", topic.name, kwargs)


pub.subscribe(_log_all_topics, pub.ALL_TOPICS)
