from __future__ import annotations

from datetime import datetime
from pathlib import Path
from typing import Any

import numpy as np
from numpy.typing import NDArray
from scipy.signal import square
from scipy.ndimage import gaussian_filter1d
from scipy.interpolate import interp1d

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

logger = get_component_logger(__file__)


class RFGProcessor(BaseProcessor):
    """
    Radiophotonic radar processor based on RFG_Receiver_GUI algorithm.

    Processes ADC data with synchronous detection (0xF0 format) or RAW data (0xD0 format).
    Outputs two graphs:
    - Processed Signal: Frequency domain signal (3-13.67 GHz range)
    - Fourier Transform: FFT magnitude spectrum with Gaussian smoothing

    Features
    --------
    - Data interpolation to standard size
    - Meander demodulation (for RAW data)
    - Frequency segmentation
    - FFT processing with Gaussian smoothing
    - Center zeroing for artifact reduction
    """

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

        # No history accumulation needed (process single sweeps)
        self._max_history = 1

        # Pre-computed meander signal (will be initialized on first use)
        self._meandr: NDArray[np.floating] | None = None
        self._last_size: int = 0

        logger.info("RFGProcessor initialized", processor_id=self.processor_id)

    # -------------------------------------------------------------------------
    # Configuration
    # -------------------------------------------------------------------------

    def _get_default_config(self) -> dict[str, Any]:
        """Return default configuration values."""
        return {
            "data_type": "SYNC_DET",           # "RAW" or "SYNC_DET"
            "pont_in_one_fq_change": 86,       # Frequency segment size
            "gaussian_sigma": 5.0,             # FFT smoothing sigma
            "fft0_delta": 5,                   # Center zero offset
            "standard_raw_size": 64000,        # Target size for RAW data
            "standard_sync_size": 1000,        # Target size for SYNC_DET data
            "freq_start_ghz": 3.0,             # Display frequency start (GHz)
            "freq_stop_ghz": 13.67,            # Display frequency stop (GHz)
            "fq_end": 512,                     # FFT cutoff point
            "show_processed": True,            # Show processed signal graph
            "show_fourier": True,              # Show Fourier transform graph
            "normalize_signal": False,         # Normalize processed signal to [0, 1]
            "log_scale": False,                # Use logarithmic scale for signal
            "disable_interpolation": False,    # Skip interpolation (use raw data size)
            "y_max_processed": 900000.0,        # Max Y-axis value for processed signal
            "y_max_fourier": 1000.0,           # Max Y-axis value for Fourier spectrum
            "auto_scale": False,               # Auto-scale Y-axis (ignore y_max if True)
        }

    def get_ui_parameters(self) -> list[UIParameter]:
        """Return UI parameter schema for configuration."""
        cfg = self._config

        return [
            UIParameter(
                name="data_type",
                label="Тип данных",
                type="select",
                value=cfg["data_type"],
                options={"choices": ["RAW", "SYNC_DET"]},
            ),
            UIParameter(
                name="gaussian_sigma",
                label="Гауссово сглаживание (σ)",
                type="slider",
                value=cfg["gaussian_sigma"],
                options={"min": 0.1, "max": 20.0, "step": 0.1, "dtype": "float"},
            ),
            UIParameter(
                name="pont_in_one_fq_change",
                label="Размер сегмента частоты",
                type="slider",
                value=cfg["pont_in_one_fq_change"],
                options={"min": 50, "max": 200, "step": 1, "dtype": "int"},
            ),
            UIParameter(
                name="fft0_delta",
                label="Смещение центра FFT",
                type="slider",
                value=cfg["fft0_delta"],
                options={"min": 0, "max": 20, "step": 1, "dtype": "int"},
            ),
            UIParameter(
                name="fq_end",
                label="Точка отсечки FFT",
                type="slider",
                value=cfg["fq_end"],
                options={"min": 100, "max": 1000, "step": 10, "dtype": "int"},
            ),
            UIParameter(
                name="freq_start_ghz",
                label="Частота начала (ГГц)",
                type="slider",
                value=cfg["freq_start_ghz"],
                options={"min": 1.0, "max": 15.0, "step": 0.1, "dtype": "float"},
            ),
            UIParameter(
                name="freq_stop_ghz",
                label="Частота конца (ГГц)",
                type="slider",
                value=cfg["freq_stop_ghz"],
                options={"min": 1.0, "max": 15.0, "step": 0.1, "dtype": "float"},
            ),
            UIParameter(
                name="show_processed",
                label="Показать обработанный сигнал",
                type="toggle",
                value=cfg["show_processed"],
            ),
            UIParameter(
                name="show_fourier",
                label="Показать Фурье образ",
                type="toggle",
                value=cfg["show_fourier"],
            ),
            UIParameter(
                name="normalize_signal",
                label="Нормализовать сигнал",
                type="toggle",
                value=cfg["normalize_signal"],
            ),
            UIParameter(
                name="log_scale",
                label="Логарифмическая шкала",
                type="toggle",
                value=cfg["log_scale"],
            ),
            UIParameter(
                name="disable_interpolation",
                label="Без интерполяции (как FOURIER)",
                type="toggle",
                value=cfg["disable_interpolation"],
            ),
            UIParameter(
                name="auto_scale",
                label="Автоматический масштаб Y",
                type="toggle",
                value=cfg["auto_scale"],
            ),
            UIParameter(
                name="y_max_processed",
                label="Макс. амплитуда (Processed)",
                type="slider",
                value=cfg["y_max_processed"],
                options={"min": 1000.0, "max": 200000.0, "step": 1000.0, "dtype": "float"},
            ),
            UIParameter(
                name="y_max_fourier",
                label="Макс. амплитуда (Fourier)",
                type="slider",
                value=cfg["y_max_fourier"],
                options={"min": 1000.0, "max": 200000.0, "step": 1000.0, "dtype": "float"},
            ),
        ]

    # -------------------------------------------------------------------------
    # Processing
    # -------------------------------------------------------------------------

    def process_sweep(
        self,
        sweep_data: SweepData,
        calibrated_data: SweepData | None,
        vna_config: dict[str, Any],
    ) -> dict[str, Any]:
        """
        Process a single sweep of ADC data.

        Returns
        -------
        dict
            Keys: processed_signal, fourier_spectrum, freq_axis, data_type, points_processed
            Or: {"error": "..."} on failure.
        """
        try:
            # Extract raw ADC data from sweep (use real part only)
            adc_data = self._extract_adc_data(sweep_data)
            if adc_data is None or adc_data.size == 0:
                logger.warning("No valid ADC data for RFG processing")
                return {"error": "No valid ADC data"}

            data_type = self._config["data_type"]

            if data_type == "RAW":
                # Full processing with meander demodulation
                processed_signal, fourier_spectrum = self._process_raw_data(adc_data)
            elif data_type == "SYNC_DET":
                # Direct FFT processing (data already demodulated)
                processed_signal, fourier_spectrum = self._process_sync_det_data(adc_data)
            else:
                return {"error": f"Unknown data type: {data_type}"}

            # Generate frequency axis for visualization
            freq_axis = self._generate_frequency_axis(processed_signal)
            fourier_spectrum /= (2*3.14)
            return {
                "processed_signal": processed_signal.tolist(),
                "fourier_spectrum": fourier_spectrum.tolist(),
                "freq_axis": freq_axis.tolist(),
                "data_type": data_type,
                "points_processed": int(adc_data.size),
                "original_size": int(adc_data.size),
            }

        except Exception as exc:  # noqa: BLE001
            logger.error("RFG processing failed", error=repr(exc))
            return {"error": str(exc)}

    # -------------------------------------------------------------------------
    # Visualization
    # -------------------------------------------------------------------------

    def generate_plotly_config(
        self,
        processed_data: dict[str, Any],
        vna_config: dict[str, Any],  # noqa: ARG002
    ) -> dict[str, Any]:
        """
        Produce Plotly configuration for two subplots: Processed Signal + Fourier Transform.
        """
        if "error" in processed_data:
            return {
                "data": [],
                "layout": {
                    "title": "RFG Радар - Ошибка",
                    "annotations": [
                        {
                            "text": f"Ошибка: {processed_data['error']}",
                            "x": 0.5,
                            "y": 0.5,
                            "xref": "paper",
                            "yref": "paper",
                            "showarrow": False,
                        }
                    ],
                    "template": "plotly_dark",
                },
            }

        processed_signal = processed_data.get("processed_signal", [])
        fourier_spectrum = processed_data.get("fourier_spectrum", [])
        freq_axis = processed_data.get("freq_axis", [])

        # Create subplot configuration
        traces = []

        if self._config["show_processed"] and processed_signal:
            # Processed Signal trace - use absolute value as in main.py line 1043
            import numpy as np
            processed_signal_abs = np.abs(np.array(processed_signal))

            # Apply normalization if enabled
            if self._config.get("normalize_signal", False):
                max_val = np.max(processed_signal_abs)
                if max_val > 0:
                    processed_signal_abs = processed_signal_abs / max_val

            # Apply log scale if enabled
            if self._config.get("log_scale", False):
                processed_signal_abs = np.log10(processed_signal_abs + 1e-12)  # Add small epsilon to avoid log(0)

            logger.debug(
                "Processed signal stats",
                min=float(np.min(processed_signal_abs)),
                max=float(np.max(processed_signal_abs)),
                mean=float(np.mean(processed_signal_abs)),
                size=len(processed_signal_abs)
            )

            traces.append({
                "type": "scatter",
                "mode": "lines",
                "x": freq_axis,
                "y": processed_signal_abs.tolist(),
                "name": "Обработанный сигнал",
                "line": {"width": 1, "color": "cyan"},
                "xaxis": "x",
                "yaxis": "y",
            })

        if self._config["show_fourier"] and fourier_spectrum:
            # Fourier Transform trace
            fft_x = list(range(len(fourier_spectrum)))
            traces.append({
                "type": "scatter",
                "mode": "lines",
                "x": fft_x,
                "y": fourier_spectrum,
                "name": "Фурье образ",
                "line": {"width": 1, "color": "yellow"},
                "xaxis": "x2",
                "yaxis": "y2",
            })

        # Build layout with two subplots
        layout = {
            "title": (
                f"RFG Радар - {processed_data.get('data_type', 'N/A')} | "
                f"Точек: {processed_data.get('points_processed', 0)}"
            ),
            "grid": {"rows": 2, "columns": 1, "pattern": "independent"},
            "xaxis": {
                "title": "Частота (ГГц)",
                "domain": [0, 1],
                "anchor": "y",
            },
            "yaxis": {
                "title": "Амплитуда",
                "domain": [0.55, 1],
                "anchor": "x",
            },
            "xaxis2": {
                "title": "Индекс",
                "domain": [0, 1],
                "anchor": "y2",
            },
            "yaxis2": {
                "title": "Магнитуда FFT",
                "domain": [0, 0.45],
                "anchor": "x2",
            },
            "template": "plotly_dark",
            "height": 700,
            "showlegend": True,
            "hovermode": "closest",
        }

        # Apply Y-axis limits if not auto-scaling
        if not self._config.get("auto_scale", False):
            y_max_processed = float(self._config.get("y_max_processed", 90000.0))
            y_max_fourier = float(self._config.get("y_max_fourier", 60000.0))

            layout["yaxis"]["range"] = [0, y_max_processed]
            layout["yaxis2"]["range"] = [0, y_max_fourier]

            logger.debug(
                "Y-axis limits applied",
                processed_max=y_max_processed,
                fourier_max=y_max_fourier
            )

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

    # -------------------------------------------------------------------------
    # Data Processing Helpers
    # -------------------------------------------------------------------------

    def _extract_adc_data(self, sweep_data: SweepData) -> NDArray[np.floating] | None:
        """
        Extract ADC data from SweepData.

        Assumes sweep_data.points contains [(real, imag), ...] pairs.
        For ADC data, we take only the real part.
        """
        try:
            if not sweep_data.points:
                return None

            # Extract real part (ADC samples)
            arr = np.asarray(sweep_data.points, dtype=float)

            logger.info(
                "🔍 RAW INPUT DATA SHAPE",
                shape=arr.shape,
                ndim=arr.ndim,
                total_points=sweep_data.total_points,
                dtype=arr.dtype
            )

            if arr.ndim == 2 and arr.shape[1] >= 1:
                # Take first column (real part)
                adc_data = arr[:, 0]
            elif arr.ndim == 1:
                # Already 1D array
                adc_data = arr
            else:
                raise ValueError("Unexpected data shape for ADC extraction")

            logger.info(
                "📊 EXTRACTED ADC DATA",
                size=adc_data.size,
                min=float(np.min(adc_data)),
                max=float(np.max(adc_data)),
                mean=float(np.mean(adc_data)),
                non_zero_count=int(np.count_nonzero(adc_data))
            )

            return adc_data.astype(float, copy=False)

        except Exception as exc:  # noqa: BLE001
            logger.error("Failed to extract ADC data", error=repr(exc))
            return None

    def _resize_1d_interpolate(
        self,
        data: NDArray[np.floating],
        target_size: int,
    ) -> NDArray[np.floating]:
        """
        Resize 1D array using linear interpolation.

        Based on main.py resize_1d_interpolate() function.
        """
        if len(data) == target_size:
            return data

        old_indices = np.linspace(0, 1, len(data))
        new_indices = np.linspace(0, 1, target_size)

        f = interp1d(old_indices, data, kind='linear', fill_value='extrapolate')
        return f(new_indices)

    def _process_raw_data(
        self,
        adc_data: NDArray[np.floating],
    ) -> tuple[NDArray[np.floating], NDArray[np.floating]]:
        """
        Process RAW ADC data (0xD0 format).

        Pipeline (based on main.py lines 824-896):
        1. Interpolate to standard size (64000)
        2. Generate meander signal for demodulation
        3. Synchronous detection (multiply by meander)
        4. Frequency segmentation
        5. FFT processing with Gaussian smoothing

        Returns
        -------
        tuple
            (processed_signal, fourier_spectrum)
        """
        # Step 1: Resize to standard RAW size
        target_size = int(self._config["standard_raw_size"])
        data_resized = self._resize_1d_interpolate(adc_data, target_size)

        # Step 2: Generate meander signal (square wave)
        if self._meandr is None or self._last_size != target_size:
            time_idx = np.arange(1, target_size + 1)
            self._meandr = square(time_idx * np.pi)
            self._last_size = target_size
            logger.debug("Meander signal regenerated", size=target_size)

        # Step 3: Meander demodulation (synchronous detection)
        demodulated = data_resized * self._meandr

        # Step 4: Frequency segmentation
        processed_signal = self._frequency_segmentation(demodulated)

        # Step 5: FFT processing
        fourier_spectrum = self._compute_fft_spectrum(processed_signal) / (2*3.14)

        return processed_signal, fourier_spectrum

    def _process_sync_det_data(
        self,
        adc_data: NDArray[np.floating],
    ) -> tuple[NDArray[np.floating], NDArray[np.floating]]:
        """
        Process SYNC_DET data (0xF0 format).

        Pipeline (based on main.py lines 898-917):
        1. Interpolate to standard size (1000)
        2. Use data directly as signal (already demodulated)
        3. FFT processing with Gaussian smoothing

        Returns
        -------
        tuple
            (processed_signal, fourier_spectrum)
        """
        # Step 1: Optionally resize to standard SYNC_DET size
        if self._config.get("disable_interpolation", False):
            # FOURIER mode: no interpolation, use raw data size
            data_resized = adc_data
            logger.info("🚫 Interpolation disabled - using raw data size", size=adc_data.size)
        else:
            # SYNC_DET mode: interpolate to standard size
            target_size = int(self._config["standard_sync_size"])
            data_resized = self._resize_1d_interpolate(adc_data, target_size)
            logger.debug(
                "SYNC_DET data resized",
                original_size=adc_data.size,
                target_size=target_size,
                min_val=float(np.min(data_resized)),
                max_val=float(np.max(data_resized)),
                mean_val=float(np.mean(data_resized))
            )

        # Step 2: Data is already demodulated, use directly
        processed_signal = data_resized

        # Step 3: FFT processing
        fourier_spectrum = self._compute_fft_spectrum(processed_signal)

        logger.debug(
            "SYNC_DET processing complete",
            signal_size=processed_signal.size,
            fft_size=fourier_spectrum.size
        )

        return processed_signal, fourier_spectrum

    def _frequency_segmentation(
        self,
        data: NDArray[np.floating],
    ) -> NDArray[np.floating]:
        """
        Divide data into frequency segments and sum each segment.

        Based on main.py lines 866-884.

        Parameters
        ----------
        data : ndarray
            Demodulated signal

        Returns
        -------
        ndarray
            Segmented and summed signal
        """
        pont_in_one_fq = int(self._config["pont_in_one_fq_change"])

        signal_list = []
        start = 0
        segment_start_idx = 0

        for idx in range(len(data)):
            if (idx - start) > pont_in_one_fq:
                segment = data[segment_start_idx:idx]
                if segment.size > 0:
                    # Sum the segment to extract signal
                    signal_list.append(np.sum(segment))
                start = idx
                segment_start_idx = idx

        return np.array(signal_list, dtype=float)

    def _compute_fft_spectrum(
        self,
        signal: NDArray[np.floating],
    ) -> NDArray[np.floating]:
        """
        Compute FFT spectrum with Gaussian smoothing.

        Based on main.py lines 984-994.

        Parameters
        ----------
        signal : ndarray
            Input signal (processed or raw)

        Returns
        -------
        ndarray
            Smoothed FFT magnitude spectrum
        """
        fq_end = int(self._config["fq_end"])
        gaussian_sigma = float(self._config["gaussian_sigma"])
        fft0_delta = int(self._config["fft0_delta"])

        # Cut signal to FFT length
        sig_cut = signal[:fq_end] if len(signal) >= fq_end else signal

        # Take square root of absolute value (as in main.py)
        sig_cut = np.sqrt(np.abs(sig_cut))

        # Compute FFT
        F = np.fft.fft(sig_cut)
        Fshift = np.abs(np.fft.fftshift(F))

        # Zero out the center (remove DC component and nearby artifacts)
        center = len(sig_cut) // 2
        if center < len(Fshift):
            zero_start = max(center - 0, 0)
            zero_end = min(center + 1, len(Fshift))
            Fshift[zero_start:zero_end] = 0

        # Apply Gaussian smoothing
        FshiftS = gaussian_filter1d(Fshift, gaussian_sigma)

        return FshiftS

    def _generate_frequency_axis(
        self,
        signal: NDArray[np.floating],
    ) -> NDArray[np.floating]:
        """
        Generate frequency axis for visualization.

        Based on main.py lines 1041-1042: maps signal indices to 3-13.67 GHz range.

        Parameters
        ----------
        signal : ndarray
            Processed signal

        Returns
        -------
        ndarray
            Frequency axis in GHz
        """
        freq_start = float(self._config["freq_start_ghz"])
        freq_stop = float(self._config["freq_stop_ghz"])

        signal_size = signal.size
        if signal_size == 0:
            return np.array([])

        # Calculate frequency per point
        freq_range = freq_stop - freq_start
        per_point_fq = freq_range / signal_size

        # Generate frequency axis: start + (index * per_point_fq)
        freq_axis = freq_start + (np.arange(1, signal_size + 1) * per_point_fq)

        return freq_axis
