"""Govee BLE controller protocol (experimental, per-model AES keyed).

Govee H6XXX strips speak a 20-byte framed BLE protocol. Newer firmware
(2022+) additionally wraps every frame with AES-128 where the key is
derived per model. Without the correct key the controller silently
drops frames.

This module exposes the **unencrypted** frame encoder — enough to drive
older Govee firmware and useful as a scaffold if a community AES key
ends up being wired in later. The encoder is pure; key negotiation
belongs in the transport layer where it can cache per-address state.

Frame layout (20 bytes):

    ``33 05 02 RR GG BB 00 00 00 00 00 00 00 00 00 00 00 00 00 XX``

where ``XX`` is an XOR checksum of bytes 0..18.

Reference:
    * https://github.com/Freemanium/govee_btled (reverse-engineered)
    * https://github.com/Beshelmek/govee_ble_lights

Status: **experimental**. If frames are silently dropped the model likely
requires encryption — that is out of scope here and will raise a clear
error at the transport layer.
"""

from __future__ import annotations

from ledgrab.core.devices.ble_protocols import BLEProtocol

# Govee uses a single 128-bit custom service with one write characteristic.
_SERVICE_UUID = "00010203-0405-0607-0809-0a0b0c0d1910"
_WRITE_CHAR_UUID = "00010203-0405-0607-0809-0a0b0c0d2b11"

_CMD_COLOR = 0x05
_CMD_POWER = 0x01
_MODE_MANUAL = 0x02


def _clamp_byte(value: int) -> int:
    if value < 0:
        return 0
    if value > 255:
        return 255
    return value


def _frame(command: int, payload: bytes) -> bytes:
    """Wrap a command + payload in Govee's 20-byte framed format with XOR checksum."""
    if len(payload) > 17:
        raise ValueError("Govee payload must be ≤17 bytes")
    buf = bytearray(20)
    buf[0] = 0x33
    buf[1] = command & 0xFF
    buf[2 : 2 + len(payload)] = payload
    checksum = 0
    for i in range(19):
        checksum ^= buf[i]
    buf[19] = checksum & 0xFF
    return bytes(buf)


def encode_color(r: int, g: int, b: int, brightness: int = 255) -> bytes:
    """Build a Govee "set solid color" frame."""
    r = _clamp_byte(r)
    g = _clamp_byte(g)
    b = _clamp_byte(b)
    brightness = _clamp_byte(brightness)
    if brightness != 255:
        r = (r * brightness) // 255
        g = (g * brightness) // 255
        b = (b * brightness) // 255
    return _frame(_CMD_COLOR, bytes((_MODE_MANUAL, r, g, b)))


def encode_power(on: bool) -> bytes:
    """Build a Govee power on/off frame."""
    return _frame(_CMD_POWER, bytes((0x01 if on else 0x00,)))


PROTOCOL = BLEProtocol(
    family="govee",
    display_name="Govee H6XXX (unencrypted — experimental)",
    service_uuid=_SERVICE_UUID,
    write_char_uuid=_WRITE_CHAR_UUID,
    # Govee requires Write Request (with response) for reliable delivery.
    write_with_response=True,
    encode_color=encode_color,
    encode_power=encode_power,
    name_prefixes=("ihoment_H6", "Govee_H6", "Minger_H6"),
)
