import json
import logging
from home_connect_async import Appliance, HomeConnect, HomeConnectError, Events
from homeassistant.components.button import ButtonEntity
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType

from .common import Configuration, EntityBase, EntityManager
from .const import DOMAIN, HOME_CONNECT_DEVICE

_LOGGER = logging.getLogger(__name__)

async def async_setup_entry(hass:HomeAssistant , config_entry:ConfigType, async_add_entities:AddEntitiesCallback) -> None:
    """ Add buttons for passed config_entry in HA """
    #homeconnect:HomeConnect = hass.data[DOMAIN]['homeconnect']
    entry_conf:Configuration = hass.data[DOMAIN][config_entry.entry_id]
    homeconnect:HomeConnect = entry_conf["homeconnect"]
    entity_manager = EntityManager(async_add_entities, "Button")

    def add_appliance(appliance:Appliance) -> None:
        conf = entry_conf.get_config()

        if appliance.available_programs:
            entity_manager.add(StartButton(appliance, None, conf))
            entity_manager.add(StopButton(appliance, None, conf))
        if appliance.commands:
            for command in appliance.commands.values():
                # The "BSH.Common.Command.AcknowledgeEvent" command is used to acknowledge the ProgramFinished state
                if command.key not in ["BSH.Common.Command.PauseProgram", "BSH.Common.Command.ResumeProgram", "BSH.Common.Command.AcknowledgeEvent"]:
                    button = CommandButton(appliance, command.key, conf, hc_obj=command)
                    entity_manager.add(button)
        entity_manager.register()

    def remove_appliance(appliance:Appliance) -> None:
        entity_manager.remove_appliance(appliance)

    # First add the integration button
    button_name_suffix = "" if entry_conf["primary_config_entry"] else "_"+config_entry.entry_id
    async_add_entities([HomeConnectRefreshButton(homeconnect, button_name_suffix), HomeConnectDebugButton(homeconnect, button_name_suffix)])

    # Subscribe for events and register existing appliances
    homeconnect.register_callback(add_appliance, [Events.PAIRED, Events.DATA_CHANGED, Events.PROGRAM_STARTED, Events.PROGRAM_SELECTED])
    homeconnect.register_callback(remove_appliance, Events.DEPAIRED)
    for appliance in homeconnect.appliances.values():
        add_appliance(appliance)


class StartButton(EntityBase, ButtonEntity):
    """ Class for buttons that start the selected program """
    @property
    def unique_id(self) -> str:
        return f'{self.haId}_start_pause'

    @property
    def name_ext(self) -> str:
        match self.translation_key:
            case "pause_program":
                return "Pause"
            case "resume_program":
                return "Resume"
        return "Start"

    @property
    def translation_key(self) -> str:
        op_state = self._appliance.status.get("BSH.Common.Status.OperationState")
        if op_state and op_state.value == "BSH.Common.EnumType.OperationState.Run" \
            and  "BSH.Common.Command.PauseProgram" in self._appliance.commands:
            return "pause_program"
        if op_state and op_state.value == "BSH.Common.EnumType.OperationState.Pause" \
            and "BSH.Common.Command.ResumeProgram" in self._appliance.commands:
            return "resume_program"
        return "start_program"

    @property
    def available(self) -> bool:
        op_state = self._appliance.status.get("BSH.Common.Status.OperationState")
        return super().available and op_state and \
            (
                (
                    op_state.value in ["BSH.Common.EnumType.OperationState.Ready", "BSH.Common.EnumType.OperationState.Inactive" ]
                    and (
                        "BSH.Common.Status.RemoteControlStartAllowed" not in self._appliance.status or
                        self._appliance.status["BSH.Common.Status.RemoteControlStartAllowed"].value
                    )
                    and (
                        (self._appliance.selected_program or self._appliance.startonly_program)
                        and not self._appliance.active_program
                        # and self._appliance.available_programs and
                        # self._appliance.selected_program.key in self._appliance.available_programs
                    )
                )
                or (
                    op_state.value == "BSH.Common.EnumType.OperationState.Run"
                    and  "BSH.Common.Command.PauseProgram" in self._appliance.commands
                )
                or (
                    op_state.value == "BSH.Common.EnumType.OperationState.Pause"
                    and  "BSH.Common.Command.ResumeProgram" in self._appliance.commands
                )
            )


    @property
    def icon(self) -> str:
        if "BSH.Common.Command.PauseProgram" in self._appliance.commands \
            and "BSH.Common.Status.OperationState" in self._appliance.status \
            and self._appliance.status["BSH.Common.Status.OperationState"].value == "BSH.Common.EnumType.OperationState.Run":
            return "mdi:pause"
        return "mdi:play"

    async def async_press(self) -> None:
        """ Handle button press """
        try:
            op_state = self._appliance.status.get("BSH.Common.Status.OperationState")
            if op_state and op_state.value in ["BSH.Common.EnumType.OperationState.Ready", "BSH.Common.EnumType.OperationState.Inactive" ]:
                await self._appliance.async_start_program()
            elif op_state and op_state.value == "BSH.Common.EnumType.OperationState.Run":
                await self._appliance.async_pause_active_program()
            elif op_state and op_state.value == "BSH.Common.EnumType.OperationState.Pause":
                await self._appliance.async_resume_paused_program()
        except HomeConnectError as ex:
            if ex.error_description:
                raise HomeAssistantError(f"Failed to start the selected program: {ex.error_description} ({ex.code})")
            raise HomeAssistantError(f"Failed to start the selected program ({ex.code})")

    async def async_added_to_hass(self):
        """Run when this Entity has been added to HA."""
        events = [ Events.CONNECTION_CHANGED,
                   Events.DATA_CHANGED,
                   Events.PROGRAM_SELECTED,
                   Events.PROGRAM_STARTED,
                   Events.PROGRAM_FINISHED,
                   "BSH.Common.Status.*",
                   "BSH.Common.Setting.PowerState"
        ]
        self._appliance.register_callback(self.async_on_update, events)

    async def async_will_remove_from_hass(self):
        """Entity being removed from hass."""
        events = [ Events.CONNECTION_CHANGED,
                   Events.DATA_CHANGED,
                   Events.PROGRAM_SELECTED,
                   Events.PROGRAM_STARTED,
                   Events.PROGRAM_FINISHED,
                   "BSH.Common.Status.*",
                   "BSH.Common.Setting.PowerState"
        ]
        self._appliance.deregister_callback(self.async_on_update, events)

    async def async_on_update(self, appliance:Appliance, key:str, value) -> None:
        self.async_write_ha_state()


class StopButton(EntityBase, ButtonEntity):
    """ Class for buttons that start the selected program """
    @property
    def unique_id(self) -> str:
        return f'{self.haId}_stop'

    @property
    def name_ext(self) -> str:
        return "Stop"

    @property
    def translation_key(self) -> str:
        return "stop_program"

    @property
    def available(self) -> bool:
        return super().available \
        and self._appliance.active_program \
        and (
            "BSH.Common.Status.RemoteControlStartAllowed" not in self._appliance.status or
            self._appliance.status["BSH.Common.Status.RemoteControlStartAllowed"].value
        )

    @property
    def icon(self) -> str:
        return "mdi:stop"

    async def async_press(self) -> None:
        """ Handle button press """
        try:
            await self._appliance.async_stop_active_program()
        except HomeConnectError as ex:
            if ex.error_description:
                raise HomeAssistantError(f"Failed to stop the selected program: {ex.error_description} ({ex.code})")
            raise HomeAssistantError(f"Failed to stop the selected program ({ex.code})")

    async def async_added_to_hass(self):
        """Run when this Entity has been added to HA."""
        events = [ Events.CONNECTION_CHANGED,
                   Events.DATA_CHANGED,
                   Events.PROGRAM_SELECTED,
                   Events.PROGRAM_STARTED,
                   Events.PROGRAM_FINISHED,
                   "BSH.Common.Status.*",
                   "BSH.Common.Setting.PowerState"
        ]
        self._appliance.register_callback(self.async_on_update, events)

    async def async_will_remove_from_hass(self):
        """Entity being removed from hass."""
        events = [ Events.CONNECTION_CHANGED,
                   Events.DATA_CHANGED,
                   Events.PROGRAM_SELECTED,
                   Events.PROGRAM_STARTED,
                   Events.PROGRAM_FINISHED,
                   "BSH.Common.Status.*",
                   "BSH.Common.Setting.PowerState"
        ]
        self._appliance.deregister_callback(self.async_on_update, events)

    async def async_on_update(self, appliance:Appliance, key:str, value) -> None:
        self.async_write_ha_state()


class CommandButton(EntityBase, ButtonEntity):
    """ Class for running a HC command """

    @property
    def name_ext(self) -> str|None:
        return self._hc_obj.name

    @property
    def icon(self) -> str:
        return self.get_entity_setting('icon', "mdi:button-pointer")

    @property
    def available(self) -> bool:
        return super().available and self._appliance.commands and self._key in self._appliance.commands

    async def async_press(self) -> None:
        """ Handle button press """
        try:
            await self._appliance.async_send_command(self._key, True)
        except HomeConnectError as ex:
            if ex.error_description:
                raise HomeAssistantError(f"Failed to stop the selected program: {ex.error_description} ({ex.code})")
            raise HomeAssistantError(f"Failed to stop the selected program ({ex.code})")

    async def async_on_update(self, appliance:Appliance, key:str, value) -> None:
        self.async_write_ha_state()

class HomeConnectRefreshButton(ButtonEntity):
    """ Class for a button to trigger a global refresh of Home Connect data  """
    _attr_has_entity_name = True

    def __init__(self, homeconnect:HomeConnect, name_suffix:str) -> None:
        self._homeconnect = homeconnect
        self._name_suffix = name_suffix
        self.entity_id = f'home_connect.{self.unique_id}'

    @property
    def device_info(self):
        """Return information to link this entity with the correct device."""
        return HOME_CONNECT_DEVICE

    @property
    def unique_id(self) -> str:
        return "homeconnect_refresh" + self._name_suffix

    @property
    def translation_key(self) -> str:
        return "homeconnect_refresh"

    @property
    def icon(self) -> str:
        return "mdi:cloud-refresh"

    @property
    def available(self) -> bool:
        return True

    async def async_press(self) -> None:
        """ Handle button press """
        try:
            self._homeconnect.start_load_data_task(refresh=HomeConnect.RefreshMode.ALL)
        except HomeConnectError as ex:
            if ex.error_description:
                raise HomeAssistantError(f"Failed to refresh the Home Connect data: {ex.error_description} ({ex.code})")
            raise HomeAssistantError(f"Failed to refresh the Home Connect data ({ex.code})")


class HomeConnectDebugButton(ButtonEntity):
    """ Class for a button to trigger a global refresh of Home Connect data  """
    _attr_has_entity_name = True

    def __init__(self, homeconnect:HomeConnect, name_suffix:str) -> None:
        self._homeconnect = homeconnect
        self._name_suffix = name_suffix
        self.entity_id = f'home_connect.{self.unique_id}'

    @property
    def device_info(self):
        """Return information to link this entity with the correct device."""
        return HOME_CONNECT_DEVICE

    @property
    def unique_id(self) -> str:
        return "homeconnect_debug" + self._name_suffix

    # @property
    # def name(self) -> str:
    #     return "homeconnect_debug"
    #     return None
        #return "Home Connect Debug Info"

    @property
    def translation_key(self) -> str:
        return "homeconnect_debug"

    @property
    def icon(self) -> str:
        return "mdi:bug-check"

    @property
    def available(self) -> bool:
        return True

    async def async_press(self) -> None:
        """ Handle button press """
        try:
            conf = {k:v for (k,v) in self.hass.data[DOMAIN].items() if isinstance(v, (str, int, float, dict, list)) and k not in [CONF_CLIENT_ID, CONF_CLIENT_SECRET] }
            js=json.dumps(conf, indent=2, default=lambda o: '<not serializable>')
            #js=json.dumps(self.hass.data[DOMAIN], indent=2, default=lambda o: '<not serializable>')
            _LOGGER.error(js)
            js=self._homeconnect.to_json(indent=2)
            _LOGGER.error(js)
        except Exception as ex:
            raise HomeAssistantError("Failed to serialize to JSON")
