"""Deliver binary ESP-NOW messages via bridge WebSocket."""

import asyncio
from typing import List, Optional, Union

from models.device import normalize_mac
from util.binary_driver_messages import build_preset_cmd_chunks, v1_dict_to_cmd_packet
from util.espnow_wire import BROADCAST_MAC, pack_group_cmd

_BROADCAST_HEX = "ffffffffffff"


async def deliver_binary_packets(
    sender,
    packets: List[bytes],
    target_macs: Optional[List[str]] = None,
    *,
    delay_s: float = 0.1,
) -> int:
    """Send binary CMD packets unicast per MAC or broadcast when no targets."""
    if not packets:
        return 0
    deliveries = 0
    if not target_macs:
        for pkt in packets:
            if await sender.send(pkt, addr=_BROADCAST_HEX):
                deliveries += 1
            await asyncio.sleep(delay_s)
        return deliveries

    seen = set()
    ordered: List[str] = []
    for raw in target_macs:
        m = normalize_mac(str(raw)) if raw else None
        if not m or m in seen:
            continue
        seen.add(m)
        ordered.append(m)

    for pkt in packets:
        for mac in ordered:
            if await sender.send(pkt, addr=mac):
                deliveries += 1
        await asyncio.sleep(delay_s)
    return deliveries


async def deliver_group_binary_packets(
    sender,
    group_id: str,
    packets: List[bytes],
    *,
    delay_s: float = 0.1,
) -> int:
    """Broadcast GROUP_CMD packets (one ESP-NOW send per packet)."""
    from util.espnow_wire import parse_cmd

    deliveries = 0
    for pkt in packets:
        env, save = parse_cmd(pkt)
        if env is None:
            continue
        try:
            g_pkt = pack_group_cmd(str(group_id), env, save=save)
        except ValueError:
            continue
        if await sender.send(g_pkt, addr=_BROADCAST_HEX):
            deliveries += 1
        await asyncio.sleep(delay_s)
    return deliveries


async def deliver_preset_broadcast_then_per_device(
    sender,
    chunk_messages,
    target_macs,
    devices_model,
    default_id,
    delay_s=0.1,
):
    """
    chunk_messages: list of v1 JSON strings OR binary CMD bytes.
    Converts JSON strings to binary when needed.
    """
    packets: List[bytes] = []
    for msg in chunk_messages:
        if isinstance(msg, (bytes, bytearray)):
            packets.append(bytes(msg))
        else:
            import json

            try:
                body = json.loads(msg)
            except Exception:
                continue
            if isinstance(body, dict):
                packets.append(v1_dict_to_cmd_packet(body))

    if not packets:
        return 0

    seen = set()
    ordered = []
    for raw in target_macs:
        m = normalize_mac(str(raw)) if raw else None
        if not m or m in seen:
            continue
        seen.add(m)
        ordered.append(m)

    deliveries = await deliver_binary_packets(
        sender, packets, ordered, delay_s=delay_s
    )

    if default_id:
        did = str(default_id)
        for mac in ordered:
            doc = devices_model.read(mac) or {}
            name = str(doc.get("name") or "").strip() or mac
            body = {"v": "1", "default": did, "save": True, "targets": [name]}
            pkt = v1_dict_to_cmd_packet(body)
            if await sender.send(pkt, addr=mac):
                deliveries += 1
            await asyncio.sleep(delay_s)

    return deliveries


async def deliver_json_messages(sender, messages, target_macs, devices_model, delay_s=0.1):
    """
    Convert v1 JSON message strings to binary CMD packets and deliver.
    Returns (delivery_count, chunk_count).
    """
    packets: List[bytes] = []
    import json

    for msg in messages:
        if isinstance(msg, (bytes, bytearray)):
            packets.append(bytes(msg))
            continue
        try:
            body = json.loads(msg)
        except Exception:
            continue
        if isinstance(body, dict):
            packets.append(v1_dict_to_cmd_packet(body))

    if not packets:
        return 0, 0

    if not target_macs:
        n = await deliver_binary_packets(sender, packets, None, delay_s=delay_s)
        return n, len(packets)

    seen = set()
    ordered_macs = []
    for raw in target_macs:
        m = normalize_mac(str(raw)) if raw else None
        if not m or m in seen:
            continue
        seen.add(m)
        ordered_macs.append(m)

    n = await deliver_binary_packets(sender, packets, ordered_macs, delay_s=delay_s)
    return n, len(packets)
