"""Resolve stream templates into publish streams."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

from ..errors import ConfigValidationError
from .models import (
    ListExpansion,
    PayloadSpec,
    RangeExpansion,
    SimulatorConfig,
    StreamConfig,
)


@dataclass(slots=True)
class ResolvedStreamConfig:
    """A fully resolved stream ready for payload builder construction."""

    stream_id: str
    broker: str
    topic: str
    interval: float
    qos: int
    retain: bool
    payload: PayloadSpec
    context: dict[str, Any]


def resolve_streams(config: SimulatorConfig) -> list[ResolvedStreamConfig]:
    """Expand all stream templates from a validated config."""

    broker_names = {broker.name for broker in config.brokers}
    resolved: list[ResolvedStreamConfig] = []
    for index, stream in enumerate(config.streams):
        if stream.broker not in broker_names:
            raise ConfigValidationError(
                "Stream "
                f"'{stream.name or stream.topic}' references unknown broker "
                f"'{stream.broker}'."
            )
        for offset, context in enumerate(_iter_contexts(stream)):
            topic = _apply_templates(stream.topic, context)
            payload = _templated_payload(stream.payload, context)
            stream_name = stream.name or topic
            stream_id = f"{stream_name}#{offset}" if stream.expand else stream_name
            resolved.append(
                ResolvedStreamConfig(
                    stream_id=f"{index}:{stream_id}",
                    broker=stream.broker,
                    topic=topic,
                    interval=stream.interval,
                    qos=stream.qos,
                    retain=stream.retain,
                    payload=payload,
                    context=context,
                )
            )
    return resolved


def _iter_contexts(stream: StreamConfig) -> list[dict[str, Any]]:
    """Return template contexts generated by a stream expansion spec."""

    expansion = stream.expand
    if expansion is None:
        return [{}]
    if isinstance(expansion, RangeExpansion):
        if expansion.inclusive:
            stop = expansion.stop + (1 if expansion.step > 0 else -1)
        else:
            stop = expansion.stop
        values = range(expansion.start, stop, expansion.step)
        return [{expansion.var: value} for value in values]
    if isinstance(expansion, ListExpansion):
        return [{expansion.var: value} for value in expansion.values]
    raise ConfigValidationError(
        f"Unsupported expansion kind: {getattr(expansion, 'kind', '?')}"
    )


def _templated_payload(payload: PayloadSpec, context: dict[str, Any]) -> PayloadSpec:
    """Apply template substitution to string values in a payload spec."""

    payload_dict = payload.model_dump(mode="python")
    templated = _apply_templates(payload_dict, context)
    return PayloadSpec.model_validate(templated)


def _apply_templates(value: Any, context: dict[str, Any]) -> Any:
    """Recursively format string values using ``str.format`` with expansion context."""

    if isinstance(value, str):
        try:
            return value.format(**context)
        except KeyError as exc:
            missing = exc.args[0]
            raise ConfigValidationError(
                f"Missing template variable '{missing}' in stream template."
            ) from exc
    if isinstance(value, list):
        return [_apply_templates(item, context) for item in value]
    if isinstance(value, dict):
        return {key: _apply_templates(item, context) for key, item in value.items()}
    return value
