"""Broker fetcher — retrieves frames and signals using the remotivelabs-broker API."""

from __future__ import annotations

import asyncio
from typing import Any

from remotivelabs.broker import BrokerClient, FrameInfo, SignalInfo
from remotivelabs.broker.auth import ApiKeyAuth, NoAuth, TokenAuth
from remotivelabs.cli.settings import settings
from remotivelabs.cli.utils.console import print_hint


def _build_auth(broker_url: str, api_key: str | None) -> ApiKeyAuth | TokenAuth | NoAuth:  # pyright: ignore[reportUnusedParameter]
    if api_key:
        return ApiKeyAuth(api_key)

    if broker_url.startswith("https"):
        token = settings.get_active_token()
        if token:
            return TokenAuth(token)
        print_hint(f"Broker URL {broker_url} starts with https, but no active token found, using no authentication.")

    return NoAuth()  # No authentication needed for non-https brokers


async def _fetch_async(broker_url: str, api_key: str | None) -> list[dict[str, Any]]:
    auth = _build_auth(broker_url, api_key)
    signals: list[dict[str, Any]] = []

    async with BrokerClient(url=broker_url, auth=auth) as client:
        namespaces = await client.list_namespaces()
        frame_infos: list[FrameInfo] = await client.list_frame_infos(*namespaces)

        for frame_info in frame_infos:
            signals.append(_frame_to_dict(frame_info))
            for signal_info in frame_info.signals.values():
                signals.append(_signal_to_dict(signal_info, frame_info))

    return signals


def _fetch_grpc_signals(broker_url: str, api_key: str | None = None) -> list[dict[str, Any]]:
    """Fetch frames and signals from the broker and return as dicts."""
    return asyncio.run(_fetch_async(broker_url, api_key))


def _frame_to_dict(frame_info: FrameInfo) -> dict[str, Any]:
    return {
        "type": "frame",
        "frame_id": None,  # frame_info.id,
        "frame_name": frame_info.name,
        "namespace": frame_info.namespace,
        "description": frame_info.description,
        "unit": "",
        "senders": frame_info.sender,
        "receivers": frame_info.receiver,
        "named_values": None,
        "min": None,
        "max": None,
        "factor": None,
        "offset": None,
        "cycle_time": frame_info.cycle_time_millis,
        "start_value": None,
        "size": None,
        "is_raw": None,
    }


def _signal_to_dict(signal_info: SignalInfo, frame_info: FrameInfo) -> dict[str, Any]:
    named_values: dict[str, str] | None = (
        {str(k): str(v) for k, v in signal_info.named_values.items()} if signal_info.named_values else None
    )
    return {
        "type": "signal",
        "frame_id": None,
        "signal_name": signal_info.name,
        "frame_name": frame_info.name,
        "namespace": signal_info.namespace,
        "description": signal_info.description,
        "unit": signal_info.unit if isinstance(signal_info.unit, str) else None,
        "senders": signal_info.sender,
        "receivers": signal_info.receiver,
        "named_values": named_values,
        "min": signal_info.min,
        "max": signal_info.max,
        "factor": signal_info.factor,
        "offset": None,  # signal_info.offset,
        "start_value": None,  # signal_info.start_value,
        "size": None,  # signal_info.size,
        "cycle_time": None,
        "is_raw": None,
    }
