from __future__ import annotations

from dataclasses import dataclass
from functools import reduce
from itertools import chain, tee
from operator import xor
from typing import ClassVar, Generic, TypeVar

from .exceptions import DecodeError

CharacteristicType = TypeVar("CharacteristicType")

_PAYLOAD_PREFIX = [0x55, 0xAA]


def pretty_name(name: str):
    data = name.split("_")
    return " ".join(f"{part[0].upper()}{part[1:]}" for part in data)


def wrap_payload(data: bytes) -> bytes:
    """Wraps the payload with the prefix and checksum."""
    payload = chain(_PAYLOAD_PREFIX, len(data).to_bytes(2, "big"), data)
    payload, payload_copy = tee(payload, 2)
    checksum = reduce(xor, payload_copy)
    return bytes(chain(payload, [checksum]))


def unwrap_payload(data: bytes) -> bytes:
    """Unwraps the payload, removing the prefix and checksum."""
    if len(data) < 5 or data[0:2] != bytes(_PAYLOAD_PREFIX):
        raise DecodeError("Invalid payload format")

    payload_len = int.from_bytes(data[2:4], "big")
    if len(data) - 5 != payload_len:
        raise DecodeError("Payload size mismatch")

    payload = data[4 : 4 + payload_len]
    checksum = data[4 + payload_len]
    checksum_expected = reduce(xor, data[:-1])

    if checksum_expected != checksum:
        raise DecodeError(f"Expected checksum {checksum_expected:x} found {checksum:x}")

    return payload


@dataclass
class Characteristic(Generic[CharacteristicType]):
    uuid: str
    name: str = ""
    registry: ClassVar[dict[str, Characteristic]] = {}

    def __set_name__(self, _, name: str):
        self.name = pretty_name(name)

    def __post_init__(self):
        self.registry[self.uuid] = self

    @classmethod
    def decode(cls, data: bytes) -> CharacteristicType:
        raise NotImplementedError(f"Decoding of {type(cls)} is not implemented")

    @classmethod
    def encode(cls, data: CharacteristicType) -> bytes:
        raise NotImplementedError(f"Encoding of {type(cls)} is not implemented")


class Service:
    uuid: ClassVar[str]
    registry: ClassVar[dict[str, type[Service]]] = {}

    def __init_subclass__(cls, /, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.registry[cls.uuid] = cls

    @classmethod
    def characteristics(cls):
        for value in vars(cls).values():
            if isinstance(value, Characteristic):
                yield value


@dataclass
class NotifyCharacteristic(Characteristic[bytes]):
    uuid: ClassVar[str] = "0000cee2-0000-1000-8000-00805f9b34fb"

    @staticmethod
    def decode(data: bytes) -> bytes:
        return unwrap_payload(data)

    @staticmethod
    def encode(data: bytes) -> bytes:
        return wrap_payload(data)


@dataclass
class WriteCharacteristic(Characteristic[bytes]):
    uuid: ClassVar[str] = "0000cee1-0000-1000-8000-00805f9b34fb"

    @staticmethod
    def decode(data: bytes) -> bytes:
        return unwrap_payload(data)

    @staticmethod
    def encode(data: bytes) -> bytes:
        return wrap_payload(data)
