"""Library to handle connection with Switchbot."""

from __future__ import annotations

import logging
from typing import Any

from .device import (
    DEVICE_SET_EXTENDED_KEY,
    DEVICE_SET_MODE_KEY,
    SwitchbotDeviceOverrideStateDuringConnection,
    update_after_operation,
)

_LOGGER = logging.getLogger(__name__)

BOT_COMMAND_HEADER = "5701"

# Bot keys
PRESS_KEY = f"{BOT_COMMAND_HEADER}00"
ON_KEY = f"{BOT_COMMAND_HEADER}01"
OFF_KEY = f"{BOT_COMMAND_HEADER}02"
DOWN_KEY = f"{BOT_COMMAND_HEADER}03"
UP_KEY = f"{BOT_COMMAND_HEADER}04"


class Switchbot(SwitchbotDeviceOverrideStateDuringConnection):
    """Representation of a Switchbot."""

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        """Switchbot Bot/WoHand constructor."""
        super().__init__(*args, **kwargs)
        self._inverse: bool = kwargs.pop("inverse_mode", False)

    @update_after_operation
    async def turn_on(self) -> bool:
        """Turn device on."""
        result = await self._send_command(ON_KEY)
        ret = self._check_command_result(result, 0, {1, 5})
        self._override_state({"isOn": True})
        _LOGGER.debug(
            "%s: Turn on result: %s -> %s",
            self.name,
            result.hex() if result else None,
            self._override_adv_data,
        )
        self._fire_callbacks()
        return ret

    @update_after_operation
    async def turn_off(self) -> bool:
        """Turn device off."""
        result = await self._send_command(OFF_KEY)
        ret = self._check_command_result(result, 0, {1, 5})
        self._override_state({"isOn": False})
        _LOGGER.debug(
            "%s: Turn off result: %s -> %s",
            self.name,
            result.hex() if result else None,
            self._override_adv_data,
        )
        self._fire_callbacks()
        return ret

    @update_after_operation
    async def hand_up(self) -> bool:
        """Raise device arm."""
        result = await self._send_command(UP_KEY)
        return self._check_command_result(result, 0, {1, 5})

    @update_after_operation
    async def hand_down(self) -> bool:
        """Lower device arm."""
        result = await self._send_command(DOWN_KEY)
        return self._check_command_result(result, 0, {1, 5})

    @update_after_operation
    async def press(self) -> bool:
        """Press command to device."""
        result = await self._send_command(PRESS_KEY)
        return self._check_command_result(result, 0, {1, 5})

    @update_after_operation
    async def set_switch_mode(
        self, switch_mode: bool = False, strength: int = 100, inverse: bool = False
    ) -> bool:
        """Change bot mode."""
        mode_key = format(switch_mode, "b") + format(inverse, "b")
        strength_key = f"{strength:0{2}x}"  # to hex with padding to double digit
        result = await self._send_command(DEVICE_SET_MODE_KEY + strength_key + mode_key)
        return self._check_command_result(result, 0, {1})

    @update_after_operation
    async def set_long_press(self, duration: int = 0) -> bool:
        """Set bot long press duration."""
        duration_key = f"{duration:0{2}x}"  # to hex with padding to double digit
        result = await self._send_command(DEVICE_SET_EXTENDED_KEY + "08" + duration_key)
        return self._check_command_result(result, 0, {1})

    async def get_basic_info(self) -> dict[str, Any] | None:
        """Get device basic settings."""
        if not (_data := await self._get_basic_info()):
            return None
        return {
            "battery": _data[1],
            "firmware": _data[2] / 10.0,
            "strength": _data[3],
            "timers": _data[8],
            "switchMode": bool(_data[9] & 16),
            "inverseDirection": bool(_data[9] & 1),
            "holdSeconds": _data[10],
        }

    def is_on(self) -> bool | None:
        """Return switch state from cache."""
        # To get actual position call update() first.
        value = self._get_adv_value("isOn")
        if value is None:
            return None

        if self._inverse:
            return not value
        return value
