import numpy as np
from pathlib import Path
from typing import Any

from vna_system.core.acquisition.sweep_buffer import SweepData
from vna_system.core.logging.logger import get_component_logger
from vna_system.core.processors.base_processor import BaseProcessor, UIParameter
from vna_system.core.settings.preset_manager import VNAMode

logger = get_component_logger(__file__)


class MagnitudeProcessor(BaseProcessor):
    """
    Compute and visualize magnitude (in dB) over frequency from calibrated sweep data.

    Pipeline
    --------
    1) Derive frequency axis from VNA config (start/stop, N points).
    2) Compute |S| in dB per point (20*log10(|complex|), clamped for |complex|==0).
    3) Generate Plotly configuration with parameter type in legend.

    Notes
    -----
    - `calibrated_data` is expected to be a `SweepData` with `.points: list[tuple[float,float]]`.
    - Parameter type (S11, S21, etc.) is extracted from VNA config for proper labeling.
    """

    def __init__(self, config_dir: Path) -> None:
        super().__init__("magnitude", config_dir)

    # ------------------------------------------------------------------ #
    # Core processing
    # ------------------------------------------------------------------ #
    def process_sweep(self, sweep_data: SweepData, calibrated_data: SweepData, vna_config: dict[str, Any]) -> dict[str, Any]:
        """
        Produce magnitude trace (dB) and ancillary info from a calibrated sweep.

        Returns
        -------
        dict[str, Any]
            {
                'frequencies': list[float],
                'magnitudes_db': list[float],
                'y_min': float,
                'y_max': float,
                'marker_enabled': bool,
                'marker_frequency': float,
                'grid_enabled': bool
            }
        """
        if not calibrated_data or not hasattr(calibrated_data, "points"):
            logger.warning("No calibrated data available for magnitude processing")
            return {"error": "No calibrated data available"}

        points: list[tuple[float, float]] = calibrated_data.points  # list of (real, imag)
        n = len(points)
        if n == 0:
            logger.warning("Calibrated sweep contains zero points")
            return {"error": "Empty calibrated sweep"}

        # Apply open air reference subtraction if enabled
        reference_data = None
        if self._config["open_air"] and self._sweep_history:
            latest_history = self._sweep_history[-1]
            reference_data = latest_history.get("reference_data")
            if reference_data is None:
                logger.warning("Open air subtraction cannot be done: reference_data is None")
                self._config["open_air"] = False

        if self._config["open_air"] and reference_data is not None:
            reference_points = getattr(reference_data, "points", None)
            if reference_points:
                points = self._subtract_reference(points, reference_points)

        # Frequency axis from VNA config (defaults if not provided)
        start_freq = float(vna_config.get("start_freq", 100e6))
        stop_freq = float(vna_config.get("stop_freq", 8.8e9))

        if n == 1:
            freqs = [start_freq]
        else:
            step = (stop_freq - start_freq) / (n - 1)
            freqs = [start_freq + i * step for i in range(n)]

        # Magnitude in dB (clamp zero magnitude to -120 dB)
        mags_db: list[float] = []
        phases_deg: list[float] = []
        real_points: list[float] = []
        imag_points: list[float] = []

        for real, imag in points:
            complex_val = complex(real, imag)
            real_points.append(float(real))
            imag_points.append(float(imag))
            mag = abs(complex_val)
            mags_db.append(20.0 * np.log10(mag) if mag > 0.0 else -120.0)
            phases_deg.append(np.degrees(np.angle(complex_val)))

        result = {
            "frequencies": freqs,
            "real_points": real_points,
            "imag_points": imag_points,
            "magnitudes_db": mags_db,
            "phases_deg": phases_deg,
            "y_min": float(self._config.get("y_min", -80)),
            "y_max": float(self._config.get("y_max", 10)),
            "autoscale": bool(self._config.get("autoscale", False)),
            "show_magnitude": bool(self._config.get("show_magnitude", True)),
            "show_phase": bool(self._config.get("show_phase", False)),
        }
        logger.debug("Magnitude sweep processed", points=n)
        return result

    def generate_plotly_config(self, processed_data: dict[str, Any], vna_config: dict[str, Any]) -> dict[str, Any]:
        """
        Build a Plotly figure config for the magnitude trace and optional phase.
        """
        if "error" in processed_data:
            return {"error": processed_data["error"]}

        freqs: list[float] = processed_data["frequencies"]
        mags_db: list[float] = processed_data["magnitudes_db"]
        phases_deg: list[float] = processed_data["phases_deg"]
        show_magnitude: bool = processed_data["show_magnitude"]
        show_phase: bool = processed_data["show_phase"]
        autoscale: bool = processed_data["autoscale"]

        # Determine the parameter type from preset mode
        mode = vna_config.get("mode")
        if isinstance(mode, VNAMode):
            parameter_type = mode.value.upper()
        elif isinstance(mode, str):
            parameter_type = mode.upper()
        else:
            parameter_type = "UNKNOWN"

        # Convert Hz to GHz for x-axis
        freqs_ghz = [f / 1e9 for f in freqs]

        # Pleasant colors
        magnitude_color = "rgb(46, 204, 113)"  # Pleasant green
        phase_color = "rgb(231, 76, 60)"  # Pleasant red/coral

        traces = []

        # Add magnitude trace if enabled
        if show_magnitude:
            traces.append({
                "x": freqs_ghz,
                "y": mags_db,
                "type": "scatter",
                "mode": "lines",
                "name": f"|{parameter_type}| Амплитуда",
                "line": {"color": magnitude_color, "width": 2},
                "yaxis": "y",
            })

        # Add phase trace if enabled
        if show_phase:
            traces.append({
                "x": freqs_ghz,
                "y": phases_deg,
                "type": "scatter",
                "mode": "lines",
                "name": f"∠{parameter_type} Фаза",
                "line": {"color": phase_color, "width": 2},
                "yaxis": "y2" if show_magnitude else "y",
            })

        # Layout configuration
        layout = {
            "title": f"Отклик {parameter_type}",
            "xaxis": {"title": "Частота (ГГц)", "showgrid": True},
            "hovermode": "x unified",
            "showlegend": True,
        }

        # Configure y-axis based on what's shown
        if show_magnitude:
            y_axis_config = {
                "title": "Амплитуда (дБ)",
                "showgrid": True,
                "side": "left",
                "titlefont": {"color": magnitude_color},
                "tickfont": {"color": magnitude_color},
            }

            # Apply autoscale or manual range
            if not autoscale:
                y_axis_config["range"] = [processed_data["y_min"], processed_data["y_max"]]

            layout["yaxis"] = y_axis_config

        # Add second y-axis for phase if both are shown
        if show_phase:
            if show_magnitude:
                # Phase on second axis (radians converted to degrees, but displayed as -π to π)
                layout["yaxis2"] = {
                    "title": "Фаза (град)",
                    "overlaying": "y",
                    "side": "right",
                    "showgrid": False,
                    "range": [-180, 180],  # -π to π in degrees
                    "titlefont": {"color": phase_color},
                    "tickfont": {"color": phase_color},
                }
            else:
                # Phase on primary axis if magnitude is hidden
                layout["yaxis"] = {
                    "title": "Фаза (град)",
                    "showgrid": True,
                    "side": "left",
                    "range": [-180, 180],  # -π to π in degrees
                    "titlefont": {"color": phase_color},
                    "tickfont": {"color": phase_color},
                }

        fig = {
            "data": traces,
            "layout": layout,
        }
        return fig

    # ------------------------------------------------------------------ #
    # UI schema
    # ------------------------------------------------------------------ #
    def get_ui_parameters(self) -> list[UIParameter]:
        """
        UI/validation schema for magnitude processor.
        """
        return [
            UIParameter(
                name="show_magnitude",
                label="Показать амплитуду",
                type="toggle",
                value=self._config.get("show_magnitude", True),
            ),
            UIParameter(
                name="show_phase",
                label="Показать фазу",
                type="toggle",
                value=self._config.get("show_phase", False),
            ),
            UIParameter(
                name="open_air",
                label="Вычесть Open Air",
                type="toggle",
                value=self._config.get("open_air", False),
            ),
            UIParameter(
                name="autoscale",
                label="Автомасштаб оси Y",
                type="toggle",
                value=self._config.get("autoscale", False),
            ),
            UIParameter(
                name="y_min",
                label="Мин. ось Y (дБ)",
                type="slider",
                value=self._config.get("y_min", -80),
                options={"min": -80, "max": 20, "step": 5, "dtype": "int"},
            ),
            UIParameter(
                name="y_max",
                label="Макс. ось Y (дБ)",
                type="slider",
                value=self._config.get("y_max", 10),
                options={"min": -20, "max": 40, "step": 5, "dtype": "int"},
            ),
        ]

    def _get_default_config(self) -> dict[str, Any]:
        """Defaults that align with the UI schema above."""
        return {
            "y_min": -80,
            "y_max": 10,
            "autoscale": False,
            "show_magnitude": True,
            "show_phase": False,
            "open_air": False,
        }

    def update_config(self, updates: dict[str, Any]) -> None:
        """
        Update configuration with validation to ensure y_max > y_min.
        """
        # Apply base update first
        super().update_config(updates)

        # Validate y_max > y_min after update
        y_min = float(self._config.get("y_min", -80))
        y_max = float(self._config.get("y_max", 10))

        if y_max <= y_min:
            # Adjust y_max to be at least 5 dB above y_min
            new_y_max = y_min + 5
            # Clamp to maximum allowed value
            max_allowed = 40
            if new_y_max > max_allowed:
                new_y_max = max_allowed
                # If we hit the ceiling, adjust y_min instead
                if new_y_max <= y_min:
                    self._config["y_min"] = new_y_max - 5

            self._config["y_max"] = new_y_max
            self.save_config()
            logger.info("Adjusted y_max to maintain y_max > y_min",
                        y_min=self._config["y_min"],
                        y_max=self._config["y_max"])

    def _subtract_reference(
        self,
        signal: list[tuple[float, float]],
        reference: list[tuple[float, float]]
    ) -> list[tuple[float, float]]:
        """
        Subtract reference from signal (complex subtraction).

        Args:
            signal: List of (real, imag) tuples
            reference: List of (real, imag) tuples

        Returns:
            List of (real, imag) tuples after subtraction
        """
        try:
            n = min(len(signal), len(reference))
            if n == 0:
                return signal

            result = []
            for i in range(n):
                sig_real, sig_imag = signal[i]
                ref_real, ref_imag = reference[i]
                result.append((sig_real - ref_real, sig_imag - ref_imag))

            logger.debug("Reference subtraction completed", points=n)
            return result
        except Exception as exc:
            logger.error("Reference subtraction failed", error=repr(exc))
            return signal
