"""Orchestration for standalone LibreVNA signal-generator sweeps."""

from __future__ import annotations

from dataclasses import replace
import logging
import time

from python_app.generator_sweep.config import (
    GeneratorSweepConfig,
    ResolvedGeneratorSweepConfig,
    resolve_generator_sweep_config,
)
from python_app.generator_sweep.pwm import HardwarePwmGate
from python_app.hardware_full.librevna_driver import GeneratorSettings, LibreVNADevice
from python_app.hardware_full.switch_drivers.gpio_uapi import GpioOutputLines

logger = logging.getLogger(__name__)


class _MarkerOutputs:
    """Own two GPIO marker signals used during generator sweeps."""

    def __init__(
        self,
        *,
        gpio_chip: str,
        sweep_start_pin: int,
        curr_step_pin: int,
        curr_step_initial_level: int,
    ) -> None:
        """Store GPIO metadata and initialize marker state."""
        self._lines = GpioOutputLines(
            chip=gpio_chip,
            offsets=[sweep_start_pin, curr_step_pin],
            consumer="generator_sweep",
        )
        self._curr_step_initial_level = curr_step_initial_level
        self._sweep_start_level = 0
        self._curr_step_level = curr_step_initial_level
        self._is_open = False

    def open(self) -> None:
        """Request GPIO lines and apply idle levels."""
        self._lines.open()
        self._is_open = True
        self.reset_to_idle()

    def close(self) -> None:
        """Release GPIO lines."""
        self._is_open = False
        self._lines.close()

    def begin_sweep(self) -> None:
        """Raise sweep marker while preserving current step level."""
        time.sleep(0.001)
        self._sweep_start_level = 1
        self._apply()

    def end_sweep(self) -> None:
        """Drop sweep marker after a completed pass."""
        self._sweep_start_level = 0
        self._apply()

    def toggle_step(self) -> None:
        """Invert current-step marker."""
        self._curr_step_level = 1 - self._curr_step_level
        self._apply()

    def reset_to_idle(self) -> None:
        """Return both markers to their configured idle state."""
        self._sweep_start_level = 0
        self._curr_step_level = self._curr_step_initial_level
        self._apply()

    def _apply(self) -> None:
        """Write current marker levels to GPIO."""
        if not self._is_open:
            return
        self._lines.set_values([self._sweep_start_level, self._curr_step_level])


class GeneratorSweepRunner:
    """Execute a signal-generator frequency sweep defined in run config."""

    def __init__(self, config: GeneratorSweepConfig) -> None:
        """Resolve configuration and prepare runtime helpers."""
        self._config: ResolvedGeneratorSweepConfig = resolve_generator_sweep_config(config)
        self._device: LibreVNADevice | None = None
        self._markers = _MarkerOutputs(
            gpio_chip=self._config.gpio_chip,
            sweep_start_pin=self._config.sweep_start_pin,
            curr_step_pin=self._config.curr_step_pin,
            curr_step_initial_level=self._config.curr_step_initial_level,
        )
        self._pwm = HardwarePwmGate(
            pin=self._config.pwm_pin,
            frequency_hz=self._config.pwm_frequency_hz,
            duty_cycle=self._config.pwm_duty_cycle,
        )
        self._base_generator_settings = GeneratorSettings(
            frequency_hz=float(self._config.frequencies_hz[0]),
            power_dbm=self._config.power_dbm,
            active_port=self._config.port,
            apply_amplitude_correction=self._config.amplitude_correction,
        )

    def run(self) -> None:
        """Open resources and execute one-shot or looping sweeps."""
        try:
            self._open()
            while True:
                self._run_single_sweep()
                if not self._config.loop:
                    break
        except KeyboardInterrupt:
            logger.info("Generator sweep interrupted by user")
        finally:
            self._close()

    def _open(self) -> None:
        """Open GPIO, PWM, and LibreVNA device connection."""
        self._markers.open()
        self._pwm.open()
        self._device = LibreVNADevice()
        self._device.connect(
            serial=self._config.serial,
            strict_protocol_version=self._config.strict_protocol_version,
            timeout_s=2.0,
        )
        self._device.generator.set_idle(timeout_s=1.0)

        logger.info(
            "Generator sweep ready: points=%d start=%dHz stop=%dHz loop=%s",
            len(self._config.frequencies_hz),
            self._config.frequencies_hz[0],
            self._config.frequencies_hz[-1],
            self._config.loop,
        )

    def _close(self) -> None:
        """Best-effort cleanup of PWM, GPIO, and device state."""
        self._pwm.disable()

        device = self._device
        if device is not None:
            try:
                if device.is_connected:
                    device.generator.set_idle(timeout_s=1.0)
            except Exception as exc:  # noqa: BLE001
                logger.warning("Failed to set LibreVNA idle mode during shutdown: %s", exc)
            finally:
                device.disconnect()
                self._device = None

        try:
            self._markers.reset_to_idle()
        finally:
            self._markers.close()
            self._pwm.close()

    def _run_single_sweep(self) -> None:
        """Execute one pass from the first configured frequency to the last."""
        self._markers.begin_sweep()
        try:
            for index, frequency_hz in enumerate(self._config.frequencies_hz):
                self._pwm.disable()
                self._set_generator_frequency(frequency_hz)
                if index > 0:
                    self._markers.toggle_step()
                self._wait_for_generator_ready()
                self._emit_pwm_window()
        finally:
            self._pwm.disable()
            self._markers.end_sweep()

    def _set_generator_frequency(self, frequency_hz: int) -> None:
        """Transmit next generator frequency to LibreVNA."""
        device = self._require_device()
        settings = replace(self._base_generator_settings, frequency_hz=float(frequency_hz))
        device.generator.configure(settings, timeout_s=max(self._config.settle_timeout_s, 1.0))
        logger.debug("Generator frequency set to %d Hz", frequency_hz)

    def _wait_for_generator_ready(self) -> None:
        """Wait until generator lock telemetry reports ready."""
        device = self._require_device()
        device.generator.wait_until_ready(
            timeout_s=self._config.settle_timeout_s,
            poll_interval_s=self._config.status_poll_s,
        )
        if self._config.post_lock_delay_s > 0.0:
            time.sleep(self._config.post_lock_delay_s)

    def _emit_pwm_window(self) -> None:
        """Enable PWM only during the configured hold window."""
        if self._config.hold_time_s <= 0.0:
            return
        self._pwm.enable()
        try:
            time.sleep(self._config.hold_time_s)
        finally:
            self._pwm.disable()

    def _require_device(self) -> LibreVNADevice:
        """Return connected device or fail fast if runner is not open."""
        if self._device is None:
            raise RuntimeError("Generator sweep runner is not open")
        return self._device
