from __future__ import annotations

import asyncio
import re
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from aioesphomeapi.model import BluetoothGATTError

from .api_pb2 import (  # type: ignore[attr-defined]
    AlarmControlPanelCommandRequest,
    AlarmControlPanelStateResponse,
    AuthenticationRequest,
    AuthenticationResponse,
    BinarySensorStateResponse,
    BluetoothConnectionsFreeResponse,
    BluetoothDeviceClearCacheResponse,
    BluetoothDeviceConnectionResponse,
    BluetoothDevicePairingResponse,
    BluetoothDeviceRequest,
    BluetoothDeviceUnpairingResponse,
    BluetoothGATTErrorResponse,
    BluetoothGATTGetServicesDoneResponse,
    BluetoothGATTGetServicesRequest,
    BluetoothGATTGetServicesResponse,
    BluetoothGATTNotifyDataResponse,
    BluetoothGATTNotifyRequest,
    BluetoothGATTNotifyResponse,
    BluetoothGATTReadDescriptorRequest,
    BluetoothGATTReadRequest,
    BluetoothGATTReadResponse,
    BluetoothGATTWriteDescriptorRequest,
    BluetoothGATTWriteRequest,
    BluetoothGATTWriteResponse,
    BluetoothLEAdvertisementResponse,
    BluetoothLERawAdvertisementsResponse,
    BluetoothScannerSetModeRequest,
    BluetoothScannerStateResponse,
    BluetoothSetConnectionParamsRequest,
    BluetoothSetConnectionParamsResponse,
    ButtonCommandRequest,
    CameraImageRequest,
    CameraImageResponse,
    ClimateCommandRequest,
    ClimateStateResponse,
    CoverCommandRequest,
    CoverStateResponse,
    DateCommandRequest,
    DateStateResponse,
    DateTimeCommandRequest,
    DateTimeStateResponse,
    DeviceInfoRequest,
    DeviceInfoResponse,
    DisconnectRequest,
    DisconnectResponse,
    EventResponse,
    ExecuteServiceRequest,
    ExecuteServiceResponse,
    FanCommandRequest,
    FanStateResponse,
    GetTimeRequest,
    GetTimeResponse,
    HelloRequest,
    HelloResponse,
    HomeassistantActionRequest,
    HomeassistantActionResponse,
    HomeAssistantStateResponse,
    InfraredRFReceiveEvent,
    InfraredRFTransmitRawTimingsRequest,
    LightCommandRequest,
    LightStateResponse,
    ListEntitiesAlarmControlPanelResponse,
    ListEntitiesBinarySensorResponse,
    ListEntitiesButtonResponse,
    ListEntitiesCameraResponse,
    ListEntitiesClimateResponse,
    ListEntitiesCoverResponse,
    ListEntitiesDateResponse,
    ListEntitiesDateTimeResponse,
    ListEntitiesDoneResponse,
    ListEntitiesEventResponse,
    ListEntitiesFanResponse,
    ListEntitiesInfraredResponse,
    ListEntitiesLightResponse,
    ListEntitiesLockResponse,
    ListEntitiesMediaPlayerResponse,
    ListEntitiesNumberResponse,
    ListEntitiesRadioFrequencyResponse,
    ListEntitiesRequest,
    ListEntitiesSelectResponse,
    ListEntitiesSensorResponse,
    ListEntitiesServicesResponse,
    ListEntitiesSirenResponse,
    ListEntitiesSwitchResponse,
    ListEntitiesTextResponse,
    ListEntitiesTextSensorResponse,
    ListEntitiesTimeResponse,
    ListEntitiesUpdateResponse,
    ListEntitiesValveResponse,
    ListEntitiesWaterHeaterResponse,
    LockCommandRequest,
    LockStateResponse,
    MediaPlayerCommandRequest,
    MediaPlayerStateResponse,
    NoiseEncryptionSetKeyRequest,
    NoiseEncryptionSetKeyResponse,
    NumberCommandRequest,
    NumberStateResponse,
    PingRequest,
    PingResponse,
    SelectCommandRequest,
    SelectStateResponse,
    SensorStateResponse,
    SerialProxyConfigureRequest,
    SerialProxyDataReceived,
    SerialProxyGetModemPinsRequest,
    SerialProxyGetModemPinsResponse,
    SerialProxyRequest,
    SerialProxyRequestResponse,
    SerialProxySetModemPinsRequest,
    SerialProxyWriteRequest,
    SirenCommandRequest,
    SirenStateResponse,
    SubscribeBluetoothConnectionsFreeRequest,
    SubscribeBluetoothLEAdvertisementsRequest,
    SubscribeHomeassistantServicesRequest,
    SubscribeHomeAssistantStateResponse,
    SubscribeHomeAssistantStatesRequest,
    SubscribeLogsRequest,
    SubscribeLogsResponse,
    SubscribeStatesRequest,
    SubscribeVoiceAssistantRequest,
    SwitchCommandRequest,
    SwitchStateResponse,
    TextCommandRequest,
    TextSensorStateResponse,
    TextStateResponse,
    TimeCommandRequest,
    TimeStateResponse,
    UnsubscribeBluetoothLEAdvertisementsRequest,
    UpdateCommandRequest,
    UpdateStateResponse,
    ValveCommandRequest,
    ValveStateResponse,
    VoiceAssistantAnnounceFinished,
    VoiceAssistantAnnounceRequest,
    VoiceAssistantAudio,
    VoiceAssistantConfigurationRequest,
    VoiceAssistantConfigurationResponse,
    VoiceAssistantEventResponse,
    VoiceAssistantRequest,
    VoiceAssistantResponse,
    VoiceAssistantSetConfiguration,
    VoiceAssistantTimerEventResponse,
    WaterHeaterCommandRequest,
    WaterHeaterStateResponse,
    ZWaveProxyFrame,
    ZWaveProxyRequest,
)

TWO_CHAR = re.compile(r".{2}")

# Base64 of 32 zero bytes: the well-known PSK that unprovisioned devices
# (encryption supported, no key set) accept for Noise handshakes so the real
# encryption key can be provisioned without being sent in plaintext. The
# ephemeral X25519 exchange protects against passive sniffing only; a publicly
# known PSK provides no authentication against an active MITM.
# Not to be confused with Home Assistant's probe key, which is base64 of
# thirty-two ASCII "0" characters (0x30 bytes, "MDAw...DA="), a deliberately
# wrong key used only to elicit the server hello; this constant is the only
# value unprovisioned devices accept.
ZERO_NOISE_PSK = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="

# Taken from esp_gatt_status_t in esp_gatt_defs.h
ESPHOME_GATT_ERRORS = {
    -1: "Not connected",  # Custom ESPHome error
    1: "Invalid handle",
    2: "Read not permitted",
    3: "Write not permitted",
    4: "Invalid PDU",
    5: "Insufficient authentication",
    6: "Request not supported",
    7: "Invalid offset",
    8: "Insufficient authorization",
    9: "Prepare queue full",
    10: "Attribute not found",
    11: "Attribute not long",
    12: "Insufficient key size",
    13: "Invalid attribute length",
    14: "Unlikely error",
    15: "Insufficient encryption",
    16: "Unsupported group type",
    17: "Insufficient resources",
    128: "Application error",
    129: "Internal error",
    130: "Wrong state",
    131: "Database full",
    132: "Busy",
    133: "Error",
    134: "Command started",
    135: "Illegal parameter",
    136: "Pending",
    137: "Auth fail",
    138: "More",
    139: "Invalid configuration",
    140: "Service started",
    141: "Encrypted no mitm",
    142: "Not encrypted",
    143: "Congested",
    144: "Duplicate registration",
    145: "Already open",
    146: "Cancel",
    224: "Stack RSP",
    225: "App RSP",
    239: "Unknown error",
    253: "CCC config error",
    254: "Procedure already in progress",
    255: "Out of range",
}


class APIConnectionError(Exception):
    pass


class APIConnectionCancelledError(APIConnectionError):
    pass


class InvalidAuthAPIError(APIConnectionError):
    pass


class ResolveAPIError(APIConnectionError):
    """Raised when a resolve error occurs."""


class ResolveTimeoutAPIError(ResolveAPIError, asyncio.TimeoutError):
    """Raised when a resolve timeout occurs."""


class ProtocolAPIError(APIConnectionError):
    pass


class RequiresEncryptionAPIError(ProtocolAPIError):
    pass


class SocketAPIError(APIConnectionError):
    pass


class SocketClosedAPIError(SocketAPIError):
    pass


class HandshakeAPIError(APIConnectionError):
    pass


class ConnectionNotEstablishedAPIError(APIConnectionError):
    pass


class BadNameAPIError(APIConnectionError):
    """Raised when a name received from the remote but does not much the expected name."""

    def __init__(self, msg: str, received_name: str) -> None:
        super().__init__(f"{msg}: received_name={received_name}")
        self.received_name = received_name


class BadMACAddressAPIError(APIConnectionError):
    """Raised when a MAC address received from the remote but does not much the expected MAC address."""

    def __init__(self, msg: str, received_name: str, received_mac: str) -> None:
        super().__init__(
            f"{msg}: received_name={received_name}, received_mac={received_mac}"
        )
        self.received_name = received_name
        self.received_mac = received_mac


class InvalidEncryptionKeyAPIError(HandshakeAPIError):
    """Raised when the encryption key is invalid."""

    def __init__(
        self,
        msg: str | None = None,
        received_name: str | None = None,
        received_mac: str | None = None,
    ) -> None:
        super().__init__(
            f"{msg}: received_name={received_name}, received_mac={received_mac}"
        )
        self.received_name = received_name
        self.received_mac = received_mac


class EncryptionErrorAPIError(InvalidEncryptionKeyAPIError):
    """Raised when an encryption error occurs after handshake."""


class EncryptionHelloAPIError(HandshakeAPIError):
    """Raised when an encryption error occurs during hello."""


class EncryptionPlaintextAPIError(HandshakeAPIError):
    """Raised when the ESP is using plaintext during noise handshake."""


class PingFailedAPIError(APIConnectionError):
    """Raised when a ping fails."""


class TimeoutAPIError(APIConnectionError):
    """Raised when a timeout occurs."""


class ReadFailedAPIError(APIConnectionError):
    """Raised when a read fails."""


class UnhandledAPIConnectionError(APIConnectionError):
    """Raised when an unhandled error occurs."""


class BluetoothConnectionDroppedError(APIConnectionError):
    """Raised when a Bluetooth connection is dropped."""


def to_human_readable_address(address: int) -> str:
    """Convert a MAC address to a human readable format."""
    return ":".join(TWO_CHAR.findall(f"{address:012X}"))


def wifi_mac_to_bluetooth_mac(wifi_mac: str) -> str:
    """Convert a WiFi MAC address to a Bluetooth MAC address.

    ESP32 devices use a single base MAC address and derive other interface
    MAC addresses from it. The Bluetooth MAC is calculated as base_mac + 2
    to the last octet.

    Args:
        wifi_mac: WiFi MAC address in format "AABBCCDDEEFF" or "aa:bb:cc:dd:ee:ff"

    Returns:
        Bluetooth MAC address in uppercase with colons (e.g., "AA:BB:CC:DD:EE:01")

    Examples:
        >>> wifi_mac_to_bluetooth_mac("AABBCCDDEEFF")
        "AA:BB:CC:DD:EE:01"
        >>> wifi_mac_to_bluetooth_mac("aa:bb:cc:dd:ee:ff")
        "AA:BB:CC:DD:EE:01"
        >>> wifi_mac_to_bluetooth_mac("AA:BB:CC:DD:EE:FE")
        "AA:BB:CC:DD:EE:00"

    """
    # Remove colons and convert to uppercase
    clean_mac = wifi_mac.replace(":", "").upper()

    # Validate MAC address format
    if len(clean_mac) != 12 or not all(c in "0123456789ABCDEF" for c in clean_mac):
        msg = f"Invalid MAC address format: {wifi_mac}"
        raise ValueError(msg)

    # Extract the last octet and add 2
    last_octet = int(clean_mac[-2:], 16)
    new_last_octet = (last_octet + 2) % 256

    # Build the new MAC address
    bt_mac = clean_mac[:-2] + f"{new_last_octet:02X}"

    # Always return with colons
    return ":".join(TWO_CHAR.findall(bt_mac))


def to_human_readable_gatt_error(error: int) -> str:
    """Convert a GATT error to a human readable format."""
    return ESPHOME_GATT_ERRORS.get(error, "Unknown error")


class BluetoothGATTAPIError(APIConnectionError):
    def __init__(self, error: BluetoothGATTError) -> None:
        super().__init__(
            f"Bluetooth GATT Error "
            f"address={to_human_readable_address(error.address)} "
            f"handle={error.handle} "
            f"error={error.error} "
            f"description={to_human_readable_gatt_error(error.error)}"
        )
        self.error = error


class BluetoothConnectionParamsAPIError(APIConnectionError):
    def __init__(self, address: int, error: int) -> None:
        super().__init__(
            f"Error setting BLE connection parameters for "
            f"{to_human_readable_address(address)}: "
            f"{to_human_readable_gatt_error(error)} ({error})"
        )
        self.address = address
        self.error_code = error


MESSAGE_TYPE_TO_PROTO = {
    1: HelloRequest,
    2: HelloResponse,
    3: AuthenticationRequest,
    4: AuthenticationResponse,
    5: DisconnectRequest,
    6: DisconnectResponse,
    7: PingRequest,
    8: PingResponse,
    9: DeviceInfoRequest,
    10: DeviceInfoResponse,
    11: ListEntitiesRequest,
    12: ListEntitiesBinarySensorResponse,
    13: ListEntitiesCoverResponse,
    14: ListEntitiesFanResponse,
    15: ListEntitiesLightResponse,
    16: ListEntitiesSensorResponse,
    17: ListEntitiesSwitchResponse,
    18: ListEntitiesTextSensorResponse,
    19: ListEntitiesDoneResponse,
    20: SubscribeStatesRequest,
    21: BinarySensorStateResponse,
    22: CoverStateResponse,
    23: FanStateResponse,
    24: LightStateResponse,
    25: SensorStateResponse,
    26: SwitchStateResponse,
    27: TextSensorStateResponse,
    28: SubscribeLogsRequest,
    29: SubscribeLogsResponse,
    30: CoverCommandRequest,
    31: FanCommandRequest,
    32: LightCommandRequest,
    33: SwitchCommandRequest,
    34: SubscribeHomeassistantServicesRequest,
    35: HomeassistantActionRequest,
    36: GetTimeRequest,
    37: GetTimeResponse,
    38: SubscribeHomeAssistantStatesRequest,
    39: SubscribeHomeAssistantStateResponse,
    40: HomeAssistantStateResponse,
    41: ListEntitiesServicesResponse,
    42: ExecuteServiceRequest,
    43: ListEntitiesCameraResponse,
    44: CameraImageResponse,
    45: CameraImageRequest,
    46: ListEntitiesClimateResponse,
    47: ClimateStateResponse,
    48: ClimateCommandRequest,
    49: ListEntitiesNumberResponse,
    50: NumberStateResponse,
    51: NumberCommandRequest,
    52: ListEntitiesSelectResponse,
    53: SelectStateResponse,
    54: SelectCommandRequest,
    55: ListEntitiesSirenResponse,
    56: SirenStateResponse,
    57: SirenCommandRequest,
    58: ListEntitiesLockResponse,
    59: LockStateResponse,
    60: LockCommandRequest,
    61: ListEntitiesButtonResponse,
    62: ButtonCommandRequest,
    63: ListEntitiesMediaPlayerResponse,
    64: MediaPlayerStateResponse,
    65: MediaPlayerCommandRequest,
    66: SubscribeBluetoothLEAdvertisementsRequest,
    67: BluetoothLEAdvertisementResponse,
    68: BluetoothDeviceRequest,
    69: BluetoothDeviceConnectionResponse,
    70: BluetoothGATTGetServicesRequest,
    71: BluetoothGATTGetServicesResponse,
    72: BluetoothGATTGetServicesDoneResponse,
    73: BluetoothGATTReadRequest,
    74: BluetoothGATTReadResponse,
    75: BluetoothGATTWriteRequest,
    76: BluetoothGATTReadDescriptorRequest,
    77: BluetoothGATTWriteDescriptorRequest,
    78: BluetoothGATTNotifyRequest,
    79: BluetoothGATTNotifyDataResponse,
    80: SubscribeBluetoothConnectionsFreeRequest,
    81: BluetoothConnectionsFreeResponse,
    82: BluetoothGATTErrorResponse,
    83: BluetoothGATTWriteResponse,
    84: BluetoothGATTNotifyResponse,
    85: BluetoothDevicePairingResponse,
    86: BluetoothDeviceUnpairingResponse,
    87: UnsubscribeBluetoothLEAdvertisementsRequest,
    88: BluetoothDeviceClearCacheResponse,
    89: SubscribeVoiceAssistantRequest,
    90: VoiceAssistantRequest,
    91: VoiceAssistantResponse,
    92: VoiceAssistantEventResponse,
    93: BluetoothLERawAdvertisementsResponse,
    94: ListEntitiesAlarmControlPanelResponse,
    95: AlarmControlPanelStateResponse,
    96: AlarmControlPanelCommandRequest,
    97: ListEntitiesTextResponse,
    98: TextStateResponse,
    99: TextCommandRequest,
    100: ListEntitiesDateResponse,
    101: DateStateResponse,
    102: DateCommandRequest,
    103: ListEntitiesTimeResponse,
    104: TimeStateResponse,
    105: TimeCommandRequest,
    106: VoiceAssistantAudio,
    107: ListEntitiesEventResponse,
    108: EventResponse,
    109: ListEntitiesValveResponse,
    110: ValveStateResponse,
    111: ValveCommandRequest,
    112: ListEntitiesDateTimeResponse,
    113: DateTimeStateResponse,
    114: DateTimeCommandRequest,
    115: VoiceAssistantTimerEventResponse,
    116: ListEntitiesUpdateResponse,
    117: UpdateStateResponse,
    118: UpdateCommandRequest,
    119: VoiceAssistantAnnounceRequest,
    120: VoiceAssistantAnnounceFinished,
    121: VoiceAssistantConfigurationRequest,
    122: VoiceAssistantConfigurationResponse,
    123: VoiceAssistantSetConfiguration,
    124: NoiseEncryptionSetKeyRequest,
    125: NoiseEncryptionSetKeyResponse,
    126: BluetoothScannerStateResponse,
    127: BluetoothScannerSetModeRequest,
    128: ZWaveProxyFrame,
    129: ZWaveProxyRequest,
    130: HomeassistantActionResponse,
    131: ExecuteServiceResponse,
    132: ListEntitiesWaterHeaterResponse,
    133: WaterHeaterStateResponse,
    134: WaterHeaterCommandRequest,
    135: ListEntitiesInfraredResponse,
    136: InfraredRFTransmitRawTimingsRequest,
    137: InfraredRFReceiveEvent,
    138: SerialProxyConfigureRequest,
    139: SerialProxyDataReceived,
    140: SerialProxyWriteRequest,
    141: SerialProxySetModemPinsRequest,
    142: SerialProxyGetModemPinsRequest,
    143: SerialProxyGetModemPinsResponse,
    144: SerialProxyRequest,
    145: BluetoothSetConnectionParamsRequest,
    146: BluetoothSetConnectionParamsResponse,
    147: SerialProxyRequestResponse,
    148: ListEntitiesRadioFrequencyResponse,
}

MESSAGE_NUMBER_TO_PROTO = tuple(MESSAGE_TYPE_TO_PROTO.values())
