"""Radar device limit sync and clamping logic for the main window."""

from __future__ import annotations

from python_app.hardware_full.librevna_service import LibreVnaService
from python_app.hardware_full.single_radar_service import create_single_radar_service


class AppWindowRadarLimitsMixin:
    """Handle radar capability probing and dependent UI clamping."""

    def _on_radar_sweep_limits_changed(self) -> None:
        """Clamp processing frequency bounds after sweep start/stop edits."""
        self._reset_preprocess_selection_after_radar_key_change()
        if self._sync_processing_frequency_limits_with_radar():
            self._on_processing_live_settings_changed()

    def _refresh_radar_limits_from_device(self) -> bool:
        """Query native radar limits and apply them to GUI fields."""
        config = self._defaults_config
        if config.is_kamil_adc:
            return self._apply_radar_limits_to_ui(None)
        if config.is_multi_device:
            radar_service = LibreVnaService(serial=config.radar.serial or None)
        else:
            radar_service = create_single_radar_service(config)

        if isinstance(radar_service, LibreVnaService) and not radar_service.driver_available:
            raise RuntimeError("LibreVNA Python driver is not available for device limits query")

        limits = radar_service.read_device_limits()
        return self._apply_radar_limits_to_ui(limits)

    def _apply_radar_limits_to_ui(self, limits: dict[str, float | int] | None) -> bool:
        """Apply optional radar limits and clamp dependent GUI fields."""
        previous_limits = dict(self._radar_limits) if self._radar_limits is not None else None
        if limits is None:
            self._radar_limits = None
            self._radar_start_label.setText("Start Hz")
            self._radar_stop_label.setText("Stop Hz")
            self._radar_points_label.setText("Points")
            self._radar_ifbw_label.setText("IF BW Hz")
            self._radar_power_label.setText("Stimulus Power dBm")
            self._radar_limits_hint.setText("Device limits are not available.")
            self._power_input.setToolTip("Stimulus power configured in the active profile.")
            return False

        min_freq_hz = float(limits["min_frequency_hz"])
        max_freq_hz = float(limits["max_frequency_hz"])
        min_ifbw_hz = float(limits["min_ifbw_hz"])
        max_ifbw_hz = float(limits["max_ifbw_hz"])
        max_points = int(limits["max_points"])
        min_power_dbm = float(limits["min_power_dbm"])
        max_power_dbm = float(limits["max_power_dbm"])

        self._radar_limits = limits

        self._radar_start_label.setText(f"Start Hz ({min_freq_hz:g}..{max_freq_hz:g})")
        self._radar_stop_label.setText(f"Stop Hz ({min_freq_hz:g}..{max_freq_hz:g})")
        self._radar_points_label.setText(f"Points (1..{max_points:d})")
        self._radar_ifbw_label.setText(f"IF BW Hz ({min_ifbw_hz:g}..{max_ifbw_hz:g})")
        self._radar_power_label.setText(f"Stimulus Power dBm ({min_power_dbm:g}..{max_power_dbm:g})")
        self._radar_limits_hint.setText(
            f"Limits: Freq {min_freq_hz:g}..{max_freq_hz:g} Hz, Points 1..{max_points:d}, "
            f"IF BW {min_ifbw_hz:g}..{max_ifbw_hz:g} Hz, Power {min_power_dbm:g}..{max_power_dbm:g} dBm."
        )

        changed = False
        prev_start = self._start_hz_input.text().strip()
        prev_stop = self._stop_hz_input.text().strip()
        prev_points = self._points_input.text().strip()
        prev_ifbw = self._ifbw_input.text().strip()
        prev_power = self._power_input.text().strip()

        start_hz = self._clamp_line_edit_float(self._start_hz_input, min_freq_hz, max_freq_hz)
        stop_hz = self._clamp_line_edit_float(self._stop_hz_input, min_freq_hz, max_freq_hz)
        if start_hz > stop_hz:
            stop_hz = start_hz
            self._stop_hz_input.setText(f"{stop_hz:g}")
            changed = True

        self._clamp_line_edit_int(self._points_input, 1, max_points)
        self._clamp_line_edit_float(self._ifbw_input, min_ifbw_hz, max_ifbw_hz)
        self._clamp_line_edit_float(self._power_input, min_power_dbm, max_power_dbm)
        self._power_input.setToolTip(f"Device range: {min_power_dbm:g}..{max_power_dbm:g} dBm")

        changed = (
            changed
            or prev_start != self._start_hz_input.text().strip()
            or prev_stop != self._stop_hz_input.text().strip()
            or prev_points != self._points_input.text().strip()
            or prev_ifbw != self._ifbw_input.text().strip()
            or prev_power != self._power_input.text().strip()
        )

        applied_limits_changed = previous_limits != limits
        if applied_limits_changed:
            self._log(
                "Applied radar device limits: "
                f"freq={min_freq_hz:g}..{max_freq_hz:g} Hz, "
                f"points=1..{max_points}, "
                f"ifbw={min_ifbw_hz:g}..{max_ifbw_hz:g} Hz, "
                f"power={min_power_dbm:g}..{max_power_dbm:g} dBm"
            )

        adjustments: list[str] = []
        current_start = self._start_hz_input.text().strip()
        current_stop = self._stop_hz_input.text().strip()
        current_points = self._points_input.text().strip()
        current_ifbw = self._ifbw_input.text().strip()
        current_power = self._power_input.text().strip()
        if prev_start != current_start:
            adjustments.append(f"Start Hz: {prev_start or '<empty>'} -> {current_start}")
        if prev_stop != current_stop:
            adjustments.append(f"Stop Hz: {prev_stop or '<empty>'} -> {current_stop}")
        if prev_points != current_points:
            adjustments.append(f"Points: {prev_points or '<empty>'} -> {current_points}")
        if prev_ifbw != current_ifbw:
            adjustments.append(f"IF BW Hz: {prev_ifbw or '<empty>'} -> {current_ifbw}")
        if prev_power != current_power:
            adjustments.append(f"Stimulus Power dBm: {prev_power or '<empty>'} -> {current_power}")
        if adjustments:
            self._log_warning(
                "Radar fields were adjusted to satisfy device limits.",
                details="\n".join(adjustments),
            )

        self._sync_processing_frequency_limits_with_radar()
        return changed

    @staticmethod
    def _clamp_line_edit_float(widget, min_value: float, max_value: float) -> float:
        """Clamp float line-edit value to inclusive range and rewrite widget text."""
        try:
            value = float(widget.text().strip())
        except ValueError:
            value = min_value
        value = min(max(value, min_value), max_value)
        widget.setText(f"{value:g}")
        return value

    @staticmethod
    def _clamp_line_edit_int(widget, min_value: int, max_value: int) -> int:
        """Clamp integer line-edit value to inclusive range and rewrite widget text."""
        try:
            value = int(float(widget.text().strip()))
        except ValueError:
            value = min_value
        value = min(max(value, min_value), max_value)
        widget.setText(str(value))
        return value

    def _sync_processing_frequency_limits_with_radar(self) -> bool:
        """Synchronize all processing frequency widgets with radar sweep bounds."""
        bscan_changed = self._sync_bscan_frequency_limits_with_radar()
        gpr_changed = self._sync_gpr_frequency_limits_with_radar()
        return bscan_changed or gpr_changed

    def _sync_frequency_spinboxes_with_radar(self, widget_names: tuple[str, ...]) -> bool:
        """Synchronize one or more MHz spin boxes with current radar sweep bounds."""
        required_widgets = ("_start_hz_input", "_stop_hz_input", *widget_names)
        if not all(hasattr(self, widget_name) for widget_name in required_widgets):
            return False

        try:
            radar_start_hz = float(self._start_hz_input.text().strip())
            radar_stop_hz = float(self._stop_hz_input.text().strip())
        except ValueError:
            return False

        radar_min_mhz = min(radar_start_hz, radar_stop_hz) / 1_000_000.0
        radar_max_mhz = max(radar_start_hz, radar_stop_hz) / 1_000_000.0

        changed = False
        widget_labels = {
            "_bscan_start_freq_mhz": "B-scan Start MHz",
            "_bscan_stop_freq_mhz": "B-scan Stop MHz",
            "_gpr_start_freq_mhz": "GPR Start MHz",
            "_gpr_stop_freq_mhz": "GPR Stop MHz",
            "_legacy_gpr_start_freq_mhz": "Legacy GPR Start MHz",
            "_legacy_gpr_stop_freq_mhz": "Legacy GPR Stop MHz",
        }
        widgets = [getattr(self, widget_name) for widget_name in widget_names]
        previous_values = {widget_name: getattr(self, widget_name).value() for widget_name in widget_names}
        for widget in widgets:
            if widget.minimum() != radar_min_mhz or widget.maximum() != radar_max_mhz:
                changed = True
            widget.blockSignals(True)
            widget.setRange(radar_min_mhz, radar_max_mhz)
            widget.blockSignals(False)

        for widget in widgets:
            clamped_value = min(max(widget.value(), radar_min_mhz), radar_max_mhz)
            if clamped_value != widget.value():
                changed = True
                widget.blockSignals(True)
                widget.setValue(clamped_value)
                widget.blockSignals(False)
        clamped_fields = []
        for widget_name in widget_names:
            current_value = getattr(self, widget_name).value()
            previous_value = previous_values[widget_name]
            if current_value == previous_value:
                continue
            clamped_fields.append(
                f"{widget_labels.get(widget_name, widget_name)}: {previous_value:g} -> {current_value:g}"
            )
        if clamped_fields:
            self._log_warning(
                "Processing frequency limits were clamped to the active radar sweep.",
                details="\n".join(clamped_fields),
            )
        return changed

    def _sync_bscan_frequency_limits_with_radar(self) -> bool:
        """Synchronize B-scan start/stop MHz widget ranges with radar sweep bounds."""
        return self._sync_frequency_spinboxes_with_radar(
            (
                "_bscan_start_freq_mhz",
                "_bscan_stop_freq_mhz",
            )
        )

    def _sync_gpr_frequency_limits_with_radar(self) -> bool:
        """Synchronize GPR start/stop MHz widget ranges with radar sweep bounds."""
        return self._sync_frequency_spinboxes_with_radar(
            (
                "_gpr_start_freq_mhz",
                "_gpr_stop_freq_mhz",
                "_legacy_gpr_start_freq_mhz",
                "_legacy_gpr_stop_freq_mhz",
            )
        )
