# pylint: disable=unidiomatic-typecheck
from __future__ import annotations

from asyncio import Future
from typing import TYPE_CHECKING, Callable

from google.protobuf import message

from .api_pb2 import (  # type: ignore
    BluetoothConnectionsFreeResponse,
    BluetoothDeviceConnectionResponse,
    BluetoothGATTErrorResponse,
    BluetoothGATTGetServicesDoneResponse,
    BluetoothGATTGetServicesResponse,
    BluetoothGATTNotifyDataResponse,
    BluetoothGATTNotifyResponse,
    BluetoothGATTReadResponse,
    BluetoothGATTWriteResponse,
    BluetoothLEAdvertisementResponse,
    CameraImageResponse,
    HomeassistantServiceResponse,
    SubscribeHomeAssistantStateResponse,
)
from .model import (
    BluetoothLEAdvertisement,
    CameraState,
    EntityState,
    HomeassistantServiceCall,
)
from .model_conversions import SUBSCRIBE_STATES_RESPONSE_TYPES


def on_state_msg(
    on_state: Callable[[EntityState], None],
    image_stream: dict[int, list[bytes]],
    msg: message.Message,
) -> None:
    """Handle a state message."""
    msg_type = type(msg)
    if cls := SUBSCRIBE_STATES_RESPONSE_TYPES.get(msg_type):
        on_state(cls.from_pb(msg))
    elif msg_type is CameraImageResponse:
        if TYPE_CHECKING:
            assert isinstance(msg, CameraImageResponse)
        msg_key = msg.key
        data_parts: list[bytes] | None = image_stream.get(msg_key)
        if not data_parts:
            data_parts = []
            image_stream[msg_key] = data_parts

        data_parts.append(msg.data)
        if msg.done:
            # Return CameraState with the merged data
            image_data = b"".join(data_parts)
            del image_stream[msg_key]
            on_state(CameraState(key=msg.key, data=image_data))  # type: ignore[call-arg]


def on_home_assistant_service_response(
    on_service_call: Callable[[HomeassistantServiceCall], None],
    msg: HomeassistantServiceResponse,
) -> None:
    on_service_call(HomeassistantServiceCall.from_pb(msg))


def on_bluetooth_le_advertising_response(
    on_bluetooth_le_advertisement: Callable[[BluetoothLEAdvertisement], None],
    msg: BluetoothLEAdvertisementResponse,
) -> None:
    on_bluetooth_le_advertisement(BluetoothLEAdvertisement.from_pb(msg))  # type: ignore[misc]


def on_bluetooth_connections_free_response(
    on_bluetooth_connections_free_update: Callable[[int, int], None],
    msg: BluetoothConnectionsFreeResponse,
) -> None:
    on_bluetooth_connections_free_update(msg.free, msg.limit)


def on_bluetooth_gatt_notify_data_response(
    address: int,
    handle: int,
    on_bluetooth_gatt_notify: Callable[[int, bytearray], None],
    msg: BluetoothGATTNotifyDataResponse,
) -> None:
    """Handle a BluetoothGATTNotifyDataResponse message."""
    if address == msg.address and handle == msg.handle:
        on_bluetooth_gatt_notify(handle, bytearray(msg.data))


def on_subscribe_home_assistant_state_response(
    on_state_sub: Callable[[str, str | None], None],
    msg: SubscribeHomeAssistantStateResponse,
) -> None:
    on_state_sub(msg.entity_id, msg.attribute)


def on_bluetooth_device_connection_response(
    connect_future: Future[None],
    address: int,
    on_bluetooth_connection_state: Callable[[bool, int, int], None],
    msg: BluetoothDeviceConnectionResponse,
) -> None:
    """Handle a BluetoothDeviceConnectionResponse message.""" ""
    if address == msg.address:
        on_bluetooth_connection_state(msg.connected, msg.mtu, msg.error)
        # Resolve on ANY connection state since we do not want
        # to wait the whole timeout if the device disconnects
        # or we get an error.
        if not connect_future.done():
            connect_future.set_result(None)


def on_bluetooth_handle_message(
    address: int,
    handle: int,
    msg: (
        BluetoothGATTErrorResponse
        | BluetoothGATTNotifyResponse
        | BluetoothGATTReadResponse
        | BluetoothGATTWriteResponse
        | BluetoothDeviceConnectionResponse
    ),
) -> bool:
    """Filter a Bluetooth message for an address and handle."""
    if type(msg) is BluetoothDeviceConnectionResponse:
        return bool(msg.address == address)
    return bool(msg.address == address and msg.handle == handle)


def on_bluetooth_message_types(
    address: int,
    msg_types: tuple[type[message.Message], ...],
    msg: (
        BluetoothGATTErrorResponse
        | BluetoothGATTNotifyResponse
        | BluetoothGATTReadResponse
        | BluetoothGATTWriteResponse
        | BluetoothDeviceConnectionResponse
        | BluetoothGATTGetServicesResponse
        | BluetoothGATTGetServicesDoneResponse
        | BluetoothGATTErrorResponse
    ),
) -> bool:
    """Filter Bluetooth messages of a specific type and address."""
    return type(msg) in msg_types and bool(msg.address == address)
