"""Command line interface for Operation Flag and Extended Properites Management."""

from .. import devices
from ..config.device_flag import DeviceFlagBase
from ..constants import PropertyType, RelayMode, ResponseStatus
from .tools_base import ToolsBase


def _show_flag(flag: DeviceFlagBase, advanced: bool, hidden: bool):
    if flag.property_type == PropertyType.STANDARD:
        return True
    if flag.property_type == PropertyType.ADVANCED and advanced:
        return True
    if flag.property_type == PropertyType.HIDDEN and hidden:
        return True
    return False


class ToolsConfig(ToolsBase):
    """Command line interface for Operation Flag and Extended Properites Management."""

    async def do_read_config(self, address, log_stdout=None, background=False):
        """Read the operating flags and extended properties of a device.

        Usage:
            read_config <ADDRESS>|all
        """
        try:
            addresses = await self._ensure_address(
                address=address,
                name="Address",
                ask_value=not background,
                log_stdout=log_stdout,
                allow_all=True,
                match_device=True,
            )
            if not addresses:
                log_stdout("Address is required")
                return
        except ValueError:
            log_stdout("Invalid device address or device not found")
            return

        for device_address in addresses:
            device = devices[device_address]
            if not device == devices.modem:
                log_stdout(f"Reading configuration for: {device.address}")
                if await device.async_read_op_flags() == ResponseStatus.SUCCESS:
                    log_stdout("Operating flags read")
                else:
                    log_stdout("Operating flags read error")
                if await device.async_read_ext_properties() == ResponseStatus.SUCCESS:
                    log_stdout("Extended properties read")
                else:
                    log_stdout("Extended properties read error")

    async def do_print_config(
        self, address, advanced="n", hidden="n", log_stdout=None, background=False
    ):
        """Write the operating flags and extended properties to a device.

        Usage:
            read_ops_flags <ADDRESS>|all  <advanced> <hidden>

            advanced: y|n default 'n'
            hidden: y|n default 'n'
        """
        try:
            addresses = await self._ensure_address(
                address=address,
                name="Address",
                ask_value=not background,
                log_stdout=log_stdout,
                allow_all=False,
                match_device=True,
            )
            if not addresses:
                log_stdout("Address is required")
                return
        except ValueError:
            log_stdout("Invalid device address or device not found")
            return

        advanced = await self._ensure_bool(
            advanced, "Advanced", ask_value=True, log_stdout=log_stdout
        )
        hidden = await self._ensure_bool(
            hidden, "Advanced", ask_value=True, log_stdout=log_stdout
        )

        for device_address in addresses:
            device = devices[device_address]
            if device and not device == devices.modem:
                log_stdout("")
                log_stdout(f"Device: {device_address}")
                log_stdout("Property                        Value")
                log_stdout("------------------------------  ----------")
                for name in device.operating_flags:
                    op_flag = device.operating_flags[name]
                    if _show_flag(op_flag, advanced, hidden):
                        if op_flag.is_dirty:
                            name_out = f"{name}*"
                        else:
                            name_out = name
                        if op_flag.value_type == bool:
                            log_stdout(f"{name_out:30s}  {bool(op_flag.value)}")
                        else:
                            prop_value = (
                                op_flag.value if op_flag.value is not None else 0
                            )
                            log_stdout(
                                f"{name_out:30s}  0x{prop_value:02x} ({prop_value})"
                            )
                log_stdout("")
                for name in device.properties:
                    prop = device.properties[name]
                    if _show_flag(prop, advanced, hidden):
                        if prop.is_dirty:
                            name_out = f"{name}*"
                        else:
                            name_out = name
                        if isinstance(prop.value, bool):
                            log_stdout(f"{name_out:30s}  {prop.value}")
                        else:
                            prop_value = prop.value if prop.value is not None else 0
                            log_stdout(
                                f"{name_out:30s}  0x{prop_value:02x} ({prop_value:d})"
                            )
                log_stdout("")

    async def do_set_config_value(
        self, address, name, value, log_stdout=None, background=False
    ):
        """Set an operating flag or extended property for a device.

        Usage:
            set_config_value <ADDRESS> flag y|n
        """
        try:
            addresses = await self._ensure_address(
                address=address,
                name="Address",
                ask_value=not background,
                log_stdout=log_stdout,
                allow_all=False,
                match_device=True,
            )
            if not addresses:
                log_stdout("Address is required.")
                return
            address = addresses[0]
        except ValueError:
            log_stdout("Invalid device address or device not found")
            return

        device = devices[address]

        prop_names = []
        for prop_name in device.operating_flags:
            prop_names.append(prop_name)
        for prop_name in device.properties:
            prop_names.append(prop_name)

        if not prop_names:
            log_stdout(f"Device {address} has no configurable settings.")
            return

        try:
            name = await self._ensure_string(
                name, prop_names, "Property name", not background, log_stdout
            )
            if name is None:
                log_stdout("Property name is required.")
                return
        except ValueError:
            log_stdout(f"Flag {name} not found in device {address}")

        if device.operating_flags.get(name):
            prop = device.operating_flags[name]
        else:
            prop = device.properties[name]

        try:
            if prop.value_type is bool:
                value = await self._ensure_bool(
                    value, "Property value", not background, log_stdout
                )
            else:
                value = await self._ensure_byte(
                    value, "Property value", not background, log_stdout
                )
            if value is None:
                log_stdout("Property value is required.")
                return
        except ValueError:
            log_stdout(f"Invalid value for {name}")
            return

        prop.new_value = value
        log_stdout(f"Changed property {prop.name} value to {value}")
        log_stdout("Use `write_config command to write changes to the device")

    async def do_write_config(self, address, log_stdout=None, background=False):
        """Write the device config.

        Usage:
            write_config <ADDRESS>|all
        """
        try:
            addresses = await self._ensure_address(
                address=address,
                name="Address",
                ask_value=not background,
                log_stdout=log_stdout,
                allow_all=True,
                match_device=True,
            )
            if not addresses:
                log_stdout("Address is required")
                return
        except ValueError:
            log_stdout("Invalid device address or device not found")
            return

        for device_address in addresses:
            device = devices[device_address]
            if device != devices.modem:
                if await device.async_write_op_flags() == ResponseStatus.SUCCESS:
                    log_stdout("Operating flags written")
                else:
                    log_stdout("Operating flags write error")
                if await device.async_write_ext_properties() == ResponseStatus.SUCCESS:
                    log_stdout("Extended properties written")
                else:
                    log_stdout("Extended properties write error")

    async def do_set_kpl_toggle_mode(
        self, address, button, toggle_mode, log_stdout=None, background=False
    ):
        """Set the toggle mode of a KeypadLinc button.

        Usage:
            set_kpl_toggle_mode address button toggle_mode

        address: Address of a KeypadLinc device
        button: Button number to set (2-8 on an 8 button KPL, 3-6 on a 6 button KPL)
        toggle_mode: Button toggle mode (0: toggle on/off, 1: On only, 2: Off only)
        """
        try:
            addresses = await self._ensure_address(
                address=address,
                name="Address",
                ask_value=not background,
                log_stdout=log_stdout,
                allow_all=False,
                match_device=True,
            )
            if not addresses:
                log_stdout("Address is required")
                return
            address = addresses[0]
        except ValueError:
            log_stdout("Invalid device address or device not found")
            return

        device = devices[address]
        if not hasattr(device, "async_set_toggle_mode"):
            log_stdout("Device is not a KeypadLinc")
            return

        try:
            button = await self._ensure_int(
                button,
                device.groups.keys(),
                "Button",
                not background,
                log_stdout,
            )
            if button is None:
                log_stdout("A button number is required")
                return
        except ValueError:
            log_stdout("Invalid button number")
            return

        try:
            toggle_mode = await self._ensure_int(
                toggle_mode,
                [0, 1, 2],
                "Toggle mode",
                not background,
                log_stdout,
            )
            if toggle_mode is None:
                log_stdout("A toggle mode is required")
                return
        except ValueError:
            log_stdout("Invalid toggle mode")

        return await device.async_set_toggle_mode(
            button=button, toggle_mode=toggle_mode
        )

    async def do_set_kpl_radio_buttons(
        self, address, button1, button2, *args, log_stdout=None, background=False
    ):
        """Set the toggle mode of a KeypadLinc button.

        Usage:
            set_kpl_radio_buttons address button1 button2 [button3 button4 button5 button6 button7]

        address: Address of a KeypadLinc device
        button: Button number to set (2-8 on an 8 button KPL, 3-6 on a 6 button KPL)
        """
        try:
            addresses = await self._ensure_address(
                address=address,
                name="Address",
                ask_value=not background,
                log_stdout=log_stdout,
                allow_all=False,
                match_device=True,
            )
            if not addresses:
                log_stdout("Address is required")
                return
            address = addresses[0]
        except ValueError:
            log_stdout("Invalid device address or device not found")
            return
        device = devices[address]
        if not hasattr(device, "async_set_radio_buttons"):
            log_stdout("Device is not a KeypadLinc")
            return

        input_buttons = [button1, button2, *args]
        ask_values = button2 is None and not background
        buttons = []
        for button in range(0, len(device.groups)):
            try:
                input_button = input_buttons[button]
            except IndexError:
                input_button = None
            try:
                button = await self._ensure_int(
                    value=input_button,
                    values=device.groups.keys(),
                    name="Button",
                    ask_value=ask_values,
                    log_stdout=log_stdout,
                )
            except ValueError:
                if input_button is None and len(buttons) >= 2:
                    break
                log_stdout("Invalid button number")
                return
            if button is None and len(buttons) < 2:
                log_stdout("At least two buttons are required")
                return
            if button is None:
                break
            buttons.append(button)

        await device.async_set_radio_buttons(buttons=buttons)

    async def do_set_iolinc_mode(
        self, address, latching_mode, log_stdout=None, background=False
    ):
        """Set the relay mode of an IO Linc device.

        Usage:
            set_iolinc_mode address latching_mode

        address: IOLinc device address
        latching_mode: Trigger or Momentary A, B or C
            0 - Latching (Continuous)
            1 - Momentary A
            2 - Momentary B
            3 - Momentary C

        for more information see the IOLinc user manual.
        """
        try:
            addresses = await self._ensure_address(
                address=address,
                name="Address",
                ask_value=not background,
                log_stdout=log_stdout,
                allow_all=False,
                match_device=True,
            )
            if not addresses:
                log_stdout("Address is required")
                return
            address = addresses[0]
        except ValueError:
            log_stdout("Invalid device address or device not found")
            return

        device = devices[address]
        if not hasattr(device, "async_set_relay_mode"):
            log_stdout("Device is not an IOLinc device")
            return

        try:
            latching_mode = await self._ensure_int(
                latching_mode, [0, 1, 2, 3], "Latching mode", not background, log_stdout
            )
            if latching_mode is None:
                log_stdout("Value for latching mode is required")
                return
        except ValueError:
            log_stdout("Invalid value for latching mode")
            return
        await device.async_set_relay_mode(latching_mode=RelayMode(latching_mode))

    async def do_set_iolinc_delay(
        self, address, seconds, log_stdout=None, background=False
    ):
        """Set the delay of an IO Linc device when in momentary mode.

        Usage:
            set_iolinc_delay address seconds

        address: IOLinc device address
        seconds: Integer number of seconds to delay before the relay is turned off

        For more information see the IOLinc user manual.
        """
        try:
            addresses = await self._ensure_address(
                address=address,
                name="Address",
                ask_value=not background,
                log_stdout=log_stdout,
                allow_all=False,
                match_device=True,
            )
            if not addresses:
                log_stdout("Address is required")
                return
            address = addresses[0]
        except ValueError:
            log_stdout("Invalid device address or device not found")
            return

        device = devices[address]
        if not hasattr(device, "async_set_momentary_delay"):
            log_stdout("Device is not an IOLinc device")
            return

        try:
            seconds = await self._ensure_byte(
                seconds, "Delay (seconds)", not background, log_stdout
            )
            if seconds is None:
                log_stdout("Value for seconds is required")
        except ValueError:
            log_stdout("Invalid number for seconds")
            return

        await device.async_set_momentary_delay(seconds=seconds)

    async def do_get_engine_version(self, address, log_stdout=None, background=False):
        """Get the engine version of the device.

        Usage:
            get_engine_version <ADDRESS>|all
        """
        try:
            addresses = await self._ensure_address(
                address=address,
                name="Address",
                ask_value=not background,
                log_stdout=log_stdout,
                allow_all=True,
                match_device=True,
            )
            if not addresses:
                log_stdout("Address is required")
                return
        except ValueError:
            log_stdout("Invalid device address or device not found")
            return

        for device_address in addresses:
            device = devices[device_address]
            if not device == devices.modem and device.cat != 0x03:
                await device.async_get_engine_version()
