"""Validation and normalization helpers for run configuration payloads."""

from __future__ import annotations

from typing import Any

from python_app.models.run_config_schema import ComboModel, GprModel, RingEndpointModel, SwitchModel

def load_switch_payload(
    payload: dict[str, Any],
    target: SwitchModel,
) -> None:
    """Populate switch model from payload preserving defaults for missing values."""
    target.name = str(payload.get("name", target.name))
    target.driver_mode = str(payload.get("driver_mode", target.driver_mode))
    target.driver = str(payload.get("driver", target.driver))
    target.radar_port = int(payload.get("radar_port", target.radar_port))
    target.positions = int(payload.get("positions", target.positions))
    target.default_position = int(payload.get("default_position", target.default_position))
    target.gpio_chip = str(payload.get("gpio_chip", target.gpio_chip))
    target.pin_a = int(payload.get("pin_a", target.pin_a))
    target.pin_b = int(payload.get("pin_b", target.pin_b))
    target.invert_logic = bool(payload.get("invert_logic", target.invert_logic))


def load_ring_payload(payload: dict[str, Any], target: RingEndpointModel) -> None:
    """Populate ring endpoint model from payload preserving defaults."""
    target.name = str(payload.get("name", target.name))
    target.capacity = int(payload.get("capacity", target.capacity))
    target.slot_size_bytes = int(payload.get("slot_size_bytes", target.slot_size_bytes))


def validate_gpr_model(
    gpr: GprModel,
    *,
    input_switch_positions: int,
    output_switch_positions: int,
) -> None:
    """Validate stable GPR config against current switch dimensions."""
    if gpr.mode not in {"point", "extended"}:
        raise ValueError("gpr.mode must be either 'point' or 'extended'")
    if float(gpr.relative_permittivity) <= 0.0:
        raise ValueError("gpr.relative_permittivity must be > 0")

    seen_output_positions: set[int] = set()
    for entry in gpr.tx_geometry:
        output_pos = int(entry.output_pos)
        if output_pos < 0 or output_pos >= int(output_switch_positions):
            raise ValueError("gpr.tx_geometry output_pos is out of range")
        if output_pos in seen_output_positions:
            raise ValueError("gpr.tx_geometry contains duplicate output_pos")
        seen_output_positions.add(output_pos)

    seen_input_positions: set[int] = set()
    for entry in gpr.rx_geometry:
        input_pos = int(entry.input_pos)
        if input_pos < 0 or input_pos >= int(input_switch_positions):
            raise ValueError("gpr.rx_geometry input_pos is out of range")
        if input_pos in seen_input_positions:
            raise ValueError("gpr.rx_geometry contains duplicate input_pos")
        seen_input_positions.add(input_pos)


def parse_combos_from_text(text: str) -> list[ComboModel]:
    """Parse UI combos string in `input:output,input:output` format."""
    cleaned = text.strip()
    if not cleaned:
        return []

    combos: list[ComboModel] = []
    for item in cleaned.split(","):
        pair = item.strip()
        if not pair:
            continue
        if ":" not in pair:
            raise ValueError(f"Invalid combo syntax: {pair!r}. Expected input:output")

        input_text, output_text = pair.split(":", 1)
        combos.append(ComboModel(input=int(input_text.strip()), output=int(output_text.strip())))

    if not combos:
        raise ValueError("No valid combos were provided")
    return combos
