"""Module for interfacing between the menu controller and the PiSCSI Control Board hardware"""

import logging
from typing import Optional

from ctrlboard_event_handler.piscsi_profile_cycler import PiscsiProfileCycler
from ctrlboard_event_handler.piscsi_shutdown_cycler import PiscsiShutdownCycler
from ctrlboard_menu_builder import CtrlBoardMenuBuilder
from ctrlboard_hw.ctrlboard_hw_constants import CtrlBoardHardwareConstants
from ctrlboard_hw.hardware_button import HardwareButton
from ctrlboard_hw.encoder import Encoder
from observer import Observer
from piscsi.file_cmds import FileCmds
from piscsi.piscsi_cmds import PiscsiCmds
from piscsi.socket_cmds import SocketCmds
from piscsi_menu_controller import PiscsiMenuController


# pylint: disable=too-many-instance-attributes
class CtrlBoardMenuUpdateEventHandler(Observer):
    """Class interfacing the menu controller the PiSCSI Control Board hardware."""

    def __init__(
        self,
        menu_controller: PiscsiMenuController,
        sock_cmd: SocketCmds,
        piscsi_cmd: PiscsiCmds,
    ):
        self.message = None
        self._menu_controller = menu_controller
        self._menu_renderer_config = self._menu_controller.get_menu_renderer().get_config()
        self.sock_cmd = sock_cmd
        self.piscsi_cmd = piscsi_cmd
        self.context_stack = []
        self.piscsi_profile_cycler: Optional[PiscsiProfileCycler] = None
        self.piscsi_shutdown_cycler: Optional[PiscsiShutdownCycler] = None

    def update(self, updated_object):
        if isinstance(updated_object, HardwareButton):
            if updated_object.name == CtrlBoardHardwareConstants.ROTARY_BUTTON:
                menu = self._menu_controller.get_active_menu()
                info_object = menu.get_current_info_object()

                self.route_rotary_button_handler(info_object)

                self._menu_controller.get_menu_renderer().render()
            else:  # button pressed
                if updated_object.name == "Bt1":
                    self.handle_button1()
                elif updated_object.name == "Bt2":
                    self.handle_button2()
        if isinstance(updated_object, Encoder):
            active_menu = self._menu_controller.get_active_menu()
            if updated_object.direction == 1:
                if active_menu.item_selection + 1 < len(active_menu.entries):
                    self._menu_controller.get_active_menu().item_selection += 1
            if updated_object.direction == -1:
                if active_menu.item_selection - 1 >= 0:
                    active_menu.item_selection -= 1
                else:
                    active_menu.item_selection = 0
            self._menu_controller.get_menu_renderer().render()

    def update_events(self):
        """Method handling non-blocking event handling for the cycle buttons."""
        if self.piscsi_profile_cycler is not None:
            result = self.piscsi_profile_cycler.update()
            if result is not None:
                self.piscsi_profile_cycler = None
                self.context_stack = []
                self._menu_controller.segue(result)
        if self.piscsi_shutdown_cycler is not None:
            self.piscsi_shutdown_cycler.empty_messages = False
            result = self.piscsi_shutdown_cycler.update()
            if result == "return":
                self.piscsi_shutdown_cycler = None

    def handle_button1(self):
        """Method for handling the first cycle button (cycle profiles)"""
        if self.piscsi_profile_cycler is None:
            self.piscsi_profile_cycler = PiscsiProfileCycler(
                self._menu_controller, self.sock_cmd, self.piscsi_cmd, return_entry=True
            )
        else:
            self.piscsi_profile_cycler.cycle()

    def handle_button2(self):
        """Method for handling the second cycle button (cycle shutdown)"""
        if self.piscsi_shutdown_cycler is None:
            self.piscsi_shutdown_cycler = PiscsiShutdownCycler(
                self._menu_controller, self.sock_cmd, self.piscsi_cmd
            )
        else:
            self.piscsi_shutdown_cycler.cycle()

    def route_rotary_button_handler(self, info_object):
        """Method for handling the rotary button press for the menu navigation"""
        if info_object is None:
            return

        context = info_object["context"]
        action = info_object["action"]
        handler_function_name = "handle_" + context + "_" + action
        try:
            handler_function = getattr(self, handler_function_name)
            if handler_function is not None:
                handler_function(info_object)
        except AttributeError:
            log = logging.getLogger(__name__)
            log.debug(
                "Handler function [%s] not found or returned an error. Skipping.",
                str(handler_function_name),
            )

    # noinspection PyUnusedLocal
    # pylint: disable=unused-argument
    def handle_scsi_id_menu_openactionmenu(self, info_object):
        """Method handles the rotary button press with the scsi list to open the action menu."""
        context_object = self._menu_controller.get_active_menu().get_current_info_object()
        self.context_stack.append(context_object)
        self._menu_controller.segue(
            CtrlBoardMenuBuilder.ACTION_MENU,
            context_object=context_object,
            transition_attributes=self._menu_renderer_config.transition_attributes_left,
        )

    # noinspection PyUnusedLocal
    # pylint: disable=unused-argument
    def handle_action_menu_return(self, info_object):
        """Method handles the rotary button press to return from the
        action menu to the scsi list."""
        self.context_stack.pop()
        self._menu_controller.segue(
            CtrlBoardMenuBuilder.SCSI_ID_MENU,
            transition_attributes=self._menu_renderer_config.transition_attributes_right,
        )

    # noinspection PyUnusedLocal
    # pylint: disable=unused-argument
    def handle_action_menu_slot_attachinsert(self, info_object):
        """Method handles the rotary button press on attach in the action menu."""
        context_object = self._menu_controller.get_active_menu().context_object
        self.context_stack.append(context_object)
        self._menu_controller.segue(
            CtrlBoardMenuBuilder.IMAGES_MENU,
            context_object=context_object,
            transition_attributes=self._menu_renderer_config.transition_attributes_left,
        )

    # noinspection PyUnusedLocal
    def handle_action_menu_slot_detacheject(self, info_object):
        """Method handles the rotary button press on detach in the action menu."""
        context_object = self._menu_controller.get_active_menu().context_object
        self.detach_eject_scsi_id()
        self.context_stack = []
        self._menu_controller.segue(
            CtrlBoardMenuBuilder.SCSI_ID_MENU,
            context_object=context_object,
            transition_attributes=self._menu_renderer_config.transition_attributes_right,
        )

    # noinspection PyUnusedLocal
    def handle_action_menu_slot_info(self, info_object):
        """Method handles the rotary button press on 'Info' in the action menu."""
        context_object = self._menu_controller.get_active_menu().context_object
        self.context_stack.append(context_object)
        self._menu_controller.segue(
            CtrlBoardMenuBuilder.DEVICEINFO_MENU,
            transition_attributes=self._menu_renderer_config.transition_attributes_left,
            context_object=context_object,
        )

    # noinspection PyUnusedLocal
    def handle_device_info_menu_return(self, info_object):
        """Method handles the rotary button press on 'Return' in the info menu."""
        self.context_stack.pop()
        context_object = self._menu_controller.get_active_menu().context_object
        self._menu_controller.segue(
            CtrlBoardMenuBuilder.ACTION_MENU,
            transition_attributes=self._menu_renderer_config.transition_attributes_right,
            context_object=context_object,
        )

    # noinspection PyUnusedLocal
    def handle_action_menu_loadprofile(self, info_object):
        """Method handles the rotary button press on 'Load Profile' in the action menu."""
        context_object = self._menu_controller.get_active_menu().context_object
        self.context_stack.append(context_object)
        self._menu_controller.segue(
            CtrlBoardMenuBuilder.PROFILES_MENU,
            transition_attributes=self._menu_renderer_config.transition_attributes_left,
        )

    # noinspection PyUnusedLocal
    def handle_profiles_menu_loadprofile(self, info_object):
        """Method handles the rotary button press in the profile selection menu
        for selecting a profile to load."""
        if info_object is not None and "name" in info_object:
            file_cmd = FileCmds(piscsi=self.piscsi_cmd)
            result = file_cmd.read_config(file_name=info_object["name"])
            if result["status"] is True:
                self._menu_controller.show_message("Profile loaded!")
            else:
                self._menu_controller.show_message("Loading failed!")

        self.context_stack = []
        self._menu_controller.segue(
            CtrlBoardMenuBuilder.SCSI_ID_MENU,
            transition_attributes=self._menu_renderer_config.transition_attributes_right,
        )

    # noinspection PyUnusedLocal
    def handle_action_menu_shutdown(self, info_object):
        """Method handles the rotary button press on 'Shutdown' in the action menu."""
        self.piscsi_cmd.shutdown("system")
        self._menu_controller.show_message("Shutting down!", 150)
        self._menu_controller.segue(
            CtrlBoardMenuBuilder.SCSI_ID_MENU,
            transition_attributes=self._menu_renderer_config.transition_attributes_right,
        )

    # noinspection PyUnusedLocal
    def handle_images_menu_return(self, info_object):
        """Method handles the rotary button press on 'Return' in the image selection menu
        (through attach/insert)."""
        context_object = self.context_stack.pop()
        self._menu_controller.segue(
            CtrlBoardMenuBuilder.ACTION_MENU,
            context_object=context_object,
            transition_attributes=self._menu_renderer_config.transition_attributes_right,
        )

    def handle_images_menu_image_attachinsert(self, info_object):
        """Method handles the rotary button press on an image in the image selection menu
        (through attach/insert)"""
        context_object = self._menu_controller.get_active_menu().context_object
        self.attach_insert_scsi_id(info_object)
        self.context_stack = []
        self._menu_controller.segue(
            CtrlBoardMenuBuilder.SCSI_ID_MENU,
            context_object=context_object,
            transition_attributes=self._menu_renderer_config.transition_attributes_right,
        )

    def attach_insert_scsi_id(self, info_object):
        """Helper method to attach/insert an image on a scsi id given through the menu context"""
        image_name = info_object["name"]
        device_type = info_object["device_type"]
        context_object = self._menu_controller.get_active_menu().context_object
        scsi_id = context_object["scsi_id"]
        params = {"file": image_name}
        result = self.piscsi_cmd.attach_device(
            scsi_id=scsi_id, device_type=device_type, params=params
        )

        if result["status"] is False:
            self._menu_controller.show_message("Attach failed!")
        else:
            self.show_id_action_message(scsi_id, "attached")

    def detach_eject_scsi_id(self):
        """Helper method to detach/eject an image on a scsi id given through the menu context"""
        context_object = self._menu_controller.get_active_menu().context_object
        scsi_id = context_object["scsi_id"]
        device_info = self.piscsi_cmd.list_devices(scsi_id)

        if not device_info["device_list"]:
            return

        device_type = device_info["device_list"][0]["device_type"]
        image = device_info["device_list"][0]["image"]
        if device_type in ("SCHD", "SCDP", "SCLP", "SCHS"):
            result = self.piscsi_cmd.detach_by_id(scsi_id)
            if result["status"] is True:
                self.show_id_action_message(scsi_id, "detached")
            else:
                self._menu_controller.show_message("Detach failed!")
        elif device_type in ("SCRM", "SCMO", "SCCD"):
            if image:
                result = self.piscsi_cmd.eject_by_id(scsi_id)
                if result["status"] is True:
                    self.show_id_action_message(scsi_id, "ejected")
                else:
                    self._menu_controller.show_message("Eject failed!")
            else:
                result = self.piscsi_cmd.detach_by_id(scsi_id)
                if result["status"] is True:
                    self.show_id_action_message(scsi_id, "detached")
                else:
                    self._menu_controller.show_message("Detach failed!")
        else:
            log = logging.getLogger(__name__)
            log.info(
                "Device type '%s' currently unsupported for detach/eject!",
                str(device_type),
            )

    def show_id_action_message(self, scsi_id, action: str):
        """Helper method for displaying an action message in the case of an exception."""
        self._menu_controller.show_message("ID " + str(scsi_id) + " " + action + "!")
