"""Utilities used by insteon component."""

import logging

from insteonplm.devices import ALDBStatus

from homeassistant.const import CONF_ADDRESS, CONF_ENTITY_ID, ENTITY_MATCH_ALL
from homeassistant.core import callback
from homeassistant.helpers import discovery
from homeassistant.helpers.dispatcher import dispatcher_send

from .const import (
    BUTTON_PRESSED_STATE_NAME,
    DOMAIN,
    EVENT_BUTTON_OFF,
    EVENT_BUTTON_ON,
    EVENT_CONF_BUTTON,
    INSTEON_ENTITIES,
    SIGNAL_LOAD_ALDB,
    SIGNAL_PRINT_ALDB,
    SRV_ADD_ALL_LINK,
    SRV_ALL_LINK_GROUP,
    SRV_ALL_LINK_MODE,
    SRV_CONTROLLER,
    SRV_DEL_ALL_LINK,
    SRV_HOUSECODE,
    SRV_LOAD_ALDB,
    SRV_LOAD_DB_RELOAD,
    SRV_PRINT_ALDB,
    SRV_PRINT_IM_ALDB,
    SRV_SCENE_OFF,
    SRV_SCENE_ON,
    SRV_X10_ALL_LIGHTS_OFF,
    SRV_X10_ALL_LIGHTS_ON,
    SRV_X10_ALL_UNITS_OFF,
)
from .ipdb import IPDB
from .schemas import (
    ADD_ALL_LINK_SCHEMA,
    DEL_ALL_LINK_SCHEMA,
    LOAD_ALDB_SCHEMA,
    PRINT_ALDB_SCHEMA,
    TRIGGER_SCENE_SCHEMA,
    X10_HOUSECODE_SCHEMA,
)

_LOGGER = logging.getLogger(__name__)


def register_new_device_callback(hass, config, insteon_modem):
    """Register callback for new Insteon device."""

    def _fire_button_on_off_event(address, group, val):
        # Firing an event when a button is pressed.
        device = insteon_modem.devices[address.hex]
        state_name = device.states[group].name
        button = (
            "" if state_name == BUTTON_PRESSED_STATE_NAME else state_name[-1].lower()
        )
        schema = {CONF_ADDRESS: address.hex}
        if button != "":
            schema[EVENT_CONF_BUTTON] = button
        if val:
            event = EVENT_BUTTON_ON
        else:
            event = EVENT_BUTTON_OFF
        _LOGGER.debug(
            "Firing event %s with address %s and button %s", event, address.hex, button
        )
        hass.bus.fire(event, schema)

    @callback
    def async_new_insteon_device(device):
        """Detect device from transport to be delegated to platform."""
        ipdb = IPDB()
        for state_key in device.states:
            platform_info = ipdb[device.states[state_key]]
            if platform_info and platform_info.platform:
                platform = platform_info.platform

                if platform == "on_off_events":
                    device.states[state_key].register_updates(_fire_button_on_off_event)

                else:
                    _LOGGER.info(
                        "New INSTEON device: %s (%s) %s",
                        device.address,
                        device.states[state_key].name,
                        platform,
                    )

                    hass.async_create_task(
                        discovery.async_load_platform(
                            hass,
                            platform,
                            DOMAIN,
                            discovered={
                                "address": device.address.id,
                                "state_key": state_key,
                            },
                            hass_config=config,
                        )
                    )

    insteon_modem.devices.add_device_callback(async_new_insteon_device)


@callback
def async_register_services(hass, config, insteon_modem):
    """Register services used by insteon component."""

    def add_all_link(service):
        """Add an INSTEON All-Link between two devices."""
        group = service.data.get(SRV_ALL_LINK_GROUP)
        mode = service.data.get(SRV_ALL_LINK_MODE)
        link_mode = 1 if mode.lower() == SRV_CONTROLLER else 0
        insteon_modem.start_all_linking(link_mode, group)

    def del_all_link(service):
        """Delete an INSTEON All-Link between two devices."""
        group = service.data.get(SRV_ALL_LINK_GROUP)
        insteon_modem.start_all_linking(255, group)

    def load_aldb(service):
        """Load the device All-Link database."""
        entity_id = service.data[CONF_ENTITY_ID]
        reload = service.data[SRV_LOAD_DB_RELOAD]
        if entity_id.lower() == ENTITY_MATCH_ALL:
            for entity_id in hass.data[DOMAIN][INSTEON_ENTITIES]:
                _send_load_aldb_signal(entity_id, reload)
        else:
            _send_load_aldb_signal(entity_id, reload)

    def _send_load_aldb_signal(entity_id, reload):
        """Send the load All-Link database signal to INSTEON entity."""
        signal = f"{entity_id}_{SIGNAL_LOAD_ALDB}"
        dispatcher_send(hass, signal, reload)

    def print_aldb(service):
        """Print the All-Link Database for a device."""
        # For now this sends logs to the log file.
        # Future direction is to create an INSTEON control panel.
        entity_id = service.data[CONF_ENTITY_ID]
        signal = f"{entity_id}_{SIGNAL_PRINT_ALDB}"
        dispatcher_send(hass, signal)

    def print_im_aldb(service):
        """Print the All-Link Database for a device."""
        # For now this sends logs to the log file.
        # Future direction is to create an INSTEON control panel.
        print_aldb_to_log(insteon_modem.aldb)

    def x10_all_units_off(service):
        """Send the X10 All Units Off command."""
        housecode = service.data.get(SRV_HOUSECODE)
        insteon_modem.x10_all_units_off(housecode)

    def x10_all_lights_off(service):
        """Send the X10 All Lights Off command."""
        housecode = service.data.get(SRV_HOUSECODE)
        insteon_modem.x10_all_lights_off(housecode)

    def x10_all_lights_on(service):
        """Send the X10 All Lights On command."""
        housecode = service.data.get(SRV_HOUSECODE)
        insteon_modem.x10_all_lights_on(housecode)

    def scene_on(service):
        """Trigger an INSTEON scene ON."""
        group = service.data.get(SRV_ALL_LINK_GROUP)
        insteon_modem.trigger_group_on(group)

    def scene_off(service):
        """Trigger an INSTEON scene ON."""
        group = service.data.get(SRV_ALL_LINK_GROUP)
        insteon_modem.trigger_group_off(group)

    hass.services.async_register(
        DOMAIN, SRV_ADD_ALL_LINK, add_all_link, schema=ADD_ALL_LINK_SCHEMA
    )
    hass.services.async_register(
        DOMAIN, SRV_DEL_ALL_LINK, del_all_link, schema=DEL_ALL_LINK_SCHEMA
    )
    hass.services.async_register(
        DOMAIN, SRV_LOAD_ALDB, load_aldb, schema=LOAD_ALDB_SCHEMA
    )
    hass.services.async_register(
        DOMAIN, SRV_PRINT_ALDB, print_aldb, schema=PRINT_ALDB_SCHEMA
    )
    hass.services.async_register(DOMAIN, SRV_PRINT_IM_ALDB, print_im_aldb, schema=None)
    hass.services.async_register(
        DOMAIN, SRV_X10_ALL_UNITS_OFF, x10_all_units_off, schema=X10_HOUSECODE_SCHEMA
    )
    hass.services.async_register(
        DOMAIN, SRV_X10_ALL_LIGHTS_OFF, x10_all_lights_off, schema=X10_HOUSECODE_SCHEMA
    )
    hass.services.async_register(
        DOMAIN, SRV_X10_ALL_LIGHTS_ON, x10_all_lights_on, schema=X10_HOUSECODE_SCHEMA
    )
    hass.services.async_register(
        DOMAIN, SRV_SCENE_ON, scene_on, schema=TRIGGER_SCENE_SCHEMA
    )
    hass.services.async_register(
        DOMAIN, SRV_SCENE_OFF, scene_off, schema=TRIGGER_SCENE_SCHEMA
    )
    _LOGGER.debug("Insteon Services registered")


def print_aldb_to_log(aldb):
    """Print the All-Link Database to the log file."""
    _LOGGER.info("ALDB load status is %s", aldb.status.name)
    if aldb.status not in [ALDBStatus.LOADED, ALDBStatus.PARTIAL]:
        _LOGGER.warning("Device All-Link database not loaded")
        _LOGGER.warning("Use service insteon.load_aldb first")
        return

    _LOGGER.info("RecID In Use Mode HWM Group Address  Data 1 Data 2 Data 3")
    _LOGGER.info("----- ------ ---- --- ----- -------- ------ ------ ------")
    for mem_addr in aldb:
        rec = aldb[mem_addr]
        # For now we write this to the log
        # Roadmap is to create a configuration panel
        in_use = "Y" if rec.control_flags.is_in_use else "N"
        mode = "C" if rec.control_flags.is_controller else "R"
        hwm = "Y" if rec.control_flags.is_high_water_mark else "N"
        log_msg = (
            f" {rec.mem_addr:04x}    {in_use:s}     {mode:s}   {hwm:s}    "
            f"{rec.group:3d} {rec.address.human:s}   {rec.data1:3d}   "
            f"{rec.data2:3d}   {rec.data3:3d}"
        )
        _LOGGER.info(log_msg)
