from __future__ import annotations

from importlib.metadata import version
from threading import Lock
from typing import TypedDict

from bleak import BleakScanner
from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData

from k1link.artifacts import utc_now_iso

_runtime_handle_lock = Lock()
_runtime_handles: dict[str, BLEDevice] = {}


class BleDeviceRecord(TypedDict):
    macos_uuid: str
    id_kind: str
    name: str | None
    local_name: str | None
    rssi: int
    tx_power: int | None
    service_uuids: list[str]
    manufacturer_data_hex: dict[str, str]
    service_data_hex: dict[str, str]
    k1_name_candidate: bool


class BleScanResult(TypedDict):
    schema_version: int
    started_at_utc: str
    completed_at_utc: str
    duration_seconds: float
    adapter: str
    bleak_version: str
    device_count: int
    devices: list[BleDeviceRecord]


def advertisement_record(device: BLEDevice, advertisement: AdvertisementData) -> BleDeviceRecord:
    name = advertisement.local_name or device.name
    normalized_name = (name or "").casefold()
    return {
        "macos_uuid": device.address,
        "id_kind": "corebluetooth_uuid",
        "name": device.name,
        "local_name": advertisement.local_name,
        "rssi": advertisement.rssi,
        "tx_power": advertisement.tx_power,
        "service_uuids": sorted(advertisement.service_uuids),
        "manufacturer_data_hex": {
            str(company_id): data.hex()
            for company_id, data in sorted(advertisement.manufacturer_data.items())
        },
        "service_data_hex": {
            service_uuid: data.hex()
            for service_uuid, data in sorted(advertisement.service_data.items())
        },
        "k1_name_candidate": normalized_name.startswith("xgr-")
        or any(marker in normalized_name for marker in ("lixel", "xgrids", "k1")),
    }


def discovered_device(macos_uuid: str) -> BLEDevice | None:
    """Return the live CoreBluetooth handle retained by the latest explicit scan."""

    with _runtime_handle_lock:
        return _runtime_handles.get(macos_uuid)


async def scan(duration_seconds: float) -> BleScanResult:
    if duration_seconds <= 0:
        raise ValueError("duration_seconds must be positive")

    started_at = utc_now_iso()
    discovered = await BleakScanner.discover(timeout=duration_seconds, return_adv=True)
    with _runtime_handle_lock:
        _runtime_handles.clear()
        _runtime_handles.update(
            {device.address: device for device, _advertisement in discovered.values()}
        )
    devices = [
        advertisement_record(device, advertisement) for device, advertisement in discovered.values()
    ]
    devices.sort(
        key=lambda item: (not item["k1_name_candidate"], -item["rssi"], item["macos_uuid"])
    )
    return {
        "schema_version": 1,
        "started_at_utc": started_at,
        "completed_at_utc": utc_now_iso(),
        "duration_seconds": duration_seconds,
        "adapter": "CoreBluetooth",
        "bleak_version": version("bleak"),
        "device_count": len(devices),
        "devices": devices,
    }
