"""VNA high-level controller for direct protocol packets."""

from __future__ import annotations

from dataclasses import replace
import logging
from typing import Callable

from ..enums import PacketType
from ..exceptions import ParseError
from ..models import Packet, StreamHandle, SweepResult, VNADatapointPacket, VNARawPoint, VNASweepSettings
from ..session import LibreVNASession
from ..sweep.assembler import assemble_vna_sweep, datapoint_to_raw_point

logger = logging.getLogger(__name__)


class VNAController:
    """VNA operations built on direct packet protocol."""

    def __init__(self, session: LibreVNASession) -> None:
        """Bind VNA controller to active session."""
        self._session = session
        self._settings: VNASweepSettings | None = None

    def configure(self, settings: VNASweepSettings) -> None:
        """Send `SweepSettings` packet to configure VNA operation."""
        effective = settings
        if effective.sync_mode is None:
            effective = replace(effective, sync_mode=self._session.default_sync_mode)
        self._settings = effective

        self._session.clear_queue(PacketType.VNA_DATAPOINT)
        self._session.send(Packet(PacketType.SWEEP_SETTINGS, effective), require_ack=True)
        logger.info(
            "VNA configured: kind=%s start=%.3fHz stop=%.3fHz points=%d ifbw=%.3fHz ports=%s",
            effective.kind.value,
            effective.f_start_hz,
            effective.f_stop_hz,
            effective.points,
            effective.if_bandwidth_hz,
            effective.excited_ports,
        )

    def acquire(self, *, expected_points: int | None = None, timeout_s: float = 10.0) -> SweepResult:
        """Acquire one complete sweep and return assembled complex traces."""
        if self._settings is None:
            raise ParseError("VNA is not configured. Call vna.configure() before acquire().")

        settings = self._settings
        points_target = expected_points if expected_points is not None else settings.points
        if points_target <= 0:
            raise ValueError("expected_points must be > 0")

        logger.info(
            "VNA acquire start: expected_points=%d timeout=%.2fs standby=%s",
            points_target,
            timeout_s,
            settings.standby,
        )
        self._session.clear_queue(PacketType.VNA_DATAPOINT)
        if settings.standby:
            self._session.send(Packet(PacketType.INITIATE_SWEEP), require_ack=True)

        ordered = self._session.collect_indexed_payloads(
            packet_type=PacketType.VNA_DATAPOINT,
            expected_points=points_target,
            timeout_s=timeout_s,
            payload_type=VNADatapointPacket,
            payload_error="Expected decoded VNADatapointPacket payload",
        )

        device_info = self._session.get_device_info()
        result = assemble_vna_sweep(
            ordered,
            settings,
            num_ports=device_info.num_ports,
            expected_points=points_target,
        )
        logger.info("VNA acquire complete: received_points=%d", len(result.x))
        return result

    def stream(self, callback: Callable[[VNARawPoint], None]) -> StreamHandle:
        """Subscribe callback for every incoming VNA datapoint packet."""
        if self._settings is None:
            raise ParseError("VNA is not configured. Call vna.configure() before stream().")

        settings = self._settings
        num_ports = self._session.get_device_info().num_ports

        def _on_packet(packet: Packet) -> None:
            """Decode datapoint packet and forward mapped raw point to callback."""
            payload = packet.payload
            if not isinstance(payload, VNADatapointPacket):
                raise ParseError("Expected decoded VNADatapointPacket payload")
            callback(datapoint_to_raw_point(payload, settings, num_ports=num_ports))

        logger.info("VNA stream subscription started")
        return self._session.subscribe(PacketType.VNA_DATAPOINT, _on_packet)
