"""Measurement suitability checking for signal analysis.

Provides the ``check_measurement_suitability`` public API and supporting
helpers for determining whether a specific measurement will produce valid
results on a given signal.

References:
    IEEE 181-2011: Measurement applicability
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

import numpy as np

from oscura.inference.signal_intelligence.classification import classify_signal
from oscura.inference.signal_intelligence.detection import _count_edges
from oscura.inference.signal_intelligence.quality import assess_signal_quality

if TYPE_CHECKING:
    from oscura.core.types import WaveformTrace


def _get_measurement_categories() -> dict[str, list[str]]:
    """Get categorized list of measurement types.

    Returns:
        Dictionary mapping category names to measurement lists.
    """
    return {
        "frequency": ["frequency", "period"],
        "edge": ["rise_time", "fall_time"],
        "amplitude": ["amplitude", "overshoot", "undershoot", "preshoot"],
        "duty": ["duty_cycle", "pulse_width"],
        "statistical": ["mean", "rms"],
        "spectral": ["thd", "snr", "sinad", "enob", "sfdr", "fft", "psd"],
    }


def _check_dc_signal_compatibility(
    signal_type: str,
    measurement_name: str,
    categories: dict[str, list[str]],
    state: dict[str, Any],
) -> None:
    """Check if measurement is compatible with DC signals.

    Args:
        signal_type: Type of signal (e.g., "dc", "digital").
        measurement_name: Name of the measurement to check.
        categories: Dict mapping category names to measurement lists.
        state: Mutable dict with suitable, warnings, suggestions,
            expected_result.
    """
    if signal_type != "dc":
        return

    if measurement_name in categories["frequency"]:
        state["suitable"] = False
        state["warnings"].append(f"{measurement_name} measurement not suitable for DC signal")
        state["suggestions"].append("Use 'mean' or 'rms' measurements for DC signals")
        state["expected_result"] = "nan"
    elif measurement_name in categories["edge"]:
        state["suitable"] = False
        state["warnings"].append(f"{measurement_name} requires signal transitions")
        state["suggestions"].append("Signal appears to be DC with no edges")
        state["expected_result"] = "nan"
    elif measurement_name in categories["duty"]:
        state["suitable"] = False
        state["warnings"].append(f"{measurement_name} requires periodic signal")
        state["expected_result"] = "nan"


def _check_aperiodic_signal_compatibility(
    characteristics: list[str],
    measurement_name: str,
    categories: dict[str, list[str]],
    state: dict[str, Any],
) -> None:
    """Check if measurement is compatible with aperiodic signals.

    Args:
        characteristics: List of signal characteristics.
        measurement_name: Name of the measurement to check.
        categories: Dict mapping category names to measurement lists.
        state: Mutable dict with suitable, warnings, suggestions,
            expected_result, confidence.
    """
    if "aperiodic" not in characteristics:
        return

    periodic_measurements = categories["frequency"] + categories["duty"]
    if measurement_name in periodic_measurements:
        state["suitable"] = False
        state["confidence"] = 0.6
        state["warnings"].append(f"{measurement_name} requires periodic signal")
        state["suggestions"].append("Signal does not appear periodic")
        state["expected_result"] = "nan"
    elif measurement_name in categories["spectral"]:
        state["warnings"].append(
            "Spectral measurements on aperiodic signals may not show clear peaks"
        )
        state["suggestions"].append("Consider time-domain or statistical analysis")
        state["expected_result"] = "unreliable"


def _check_digital_signal_compatibility(
    signal_type: str,
    measurement_name: str,
    categories: dict[str, list[str]],
    state: dict[str, Any],
) -> None:
    """Check if measurement is compatible with digital signals.

    Args:
        signal_type: Type of signal.
        measurement_name: Name of the measurement to check.
        categories: Dict mapping category names to measurement lists.
        state: Mutable dict with warnings, suggestions, expected_result,
            confidence.
    """
    if signal_type != "digital":
        return

    if measurement_name in categories["amplitude"] and measurement_name != "amplitude":
        state["warnings"].append(
            f"{measurement_name} designed for analog signals with overshoot/ringing"
        )
        state["suggestions"].append("Digital signals may show zero overshoot/undershoot")
        state["expected_result"] = "unreliable"
        state["confidence"] = 0.5


def _check_edge_count_requirements(
    trace: WaveformTrace,
    measurement_name: str,
    categories: dict[str, list[str]],
    classification: dict[str, Any],
    state: dict[str, Any],
) -> None:
    """Check if signal has sufficient edges for edge-based measurements.

    Args:
        trace: Input waveform trace.
        measurement_name: Name of the measurement to check.
        categories: Dict mapping category names to measurement lists.
        classification: Signal classification info.
        state: Mutable dict with suitable, warnings, suggestions,
            expected_result.
    """
    edge_based = categories["edge"] + categories["duty"]
    if measurement_name not in edge_based:
        return

    edge_count = _count_edges(trace.data, classification.get("levels"))
    if edge_count < 2:
        state["suitable"] = False
        state["warnings"].append(f"{measurement_name} requires at least 2 signal edges")
        state["suggestions"].append(f"Signal has only {edge_count} detected edge(s)")
        state["expected_result"] = "nan"


def _check_quality_impacts(
    quality: dict[str, Any],
    measurement_name: str,
    categories: dict[str, list[str]],
    state: dict[str, Any],
) -> None:
    """Check how signal quality issues affect measurement suitability.

    Args:
        quality: Signal quality assessment.
        measurement_name: Name of the measurement to check.
        categories: Dict mapping category names to measurement lists.
        state: Mutable dict with warnings, expected_result, confidence.
    """
    affected_by_clipping = categories["edge"] + categories["amplitude"]

    if quality["clipping"] and measurement_name in affected_by_clipping:
        state["warnings"].append("Signal clipping detected, may affect measurement accuracy")
        if state["expected_result"] != "nan":
            state["expected_result"] = "unreliable"
        state["confidence"] = min(state["confidence"], 0.6)

    if quality["saturation"]:
        state["warnings"].append("Signal saturation detected, measurements may be unreliable")
        if state["expected_result"] != "nan":
            state["expected_result"] = "unreliable"
        state["confidence"] = min(state["confidence"], 0.5)

    if quality["snr"] is not None and quality["snr"] < 20:
        if measurement_name in categories["edge"]:
            state["warnings"].append(
                f"Low SNR ({quality['snr']:.1f} dB) may affect edge timing measurements"
            )
            state["suggestions"].append("Consider filtering signal to improve SNR")
            state["confidence"] = min(state["confidence"], 0.7)


def _check_sample_rate_adequacy(
    trace: WaveformTrace,
    measurement_name: str,
    categories: dict[str, list[str]],
    classification: dict[str, Any],
    state: dict[str, Any],
) -> None:
    """Check if sample rate is adequate for timing measurements.

    Uses both frequency estimation and spectral energy analysis near
    Nyquist to detect undersampling even when frequency detection fails
    due to aliasing.

    Args:
        trace: Input waveform trace.
        measurement_name: Name of the measurement to check.
        categories: Dict mapping category names to measurement lists.
        classification: Signal classification info.
        state: Mutable dict with warnings, suggestions, expected_result,
            confidence.
    """
    timing_measurements = categories["edge"] + categories["frequency"]
    if measurement_name not in timing_measurements:
        return

    sample_rate = trace.metadata.sample_rate

    if classification["frequency_estimate"] is not None:
        nyquist_rate = 2 * classification["frequency_estimate"]
        if sample_rate < nyquist_rate * 10:
            state["warnings"].append("Sample rate may be too low for accurate timing measurements")
            state["suggestions"].append(
                f"Recommend sample rate > {nyquist_rate * 10:.3e} Hz (10x signal frequency)"
            )
            state["expected_result"] = "unreliable"
            state["confidence"] = min(state["confidence"], 0.6)
    else:
        # Frequency estimation failed - check for energy near Nyquist
        # which suggests undersampling/aliasing
        data = trace.data
        n = len(data)
        if n >= 64:
            data_ac = data - np.mean(data)
            data_std = float(np.std(data_ac))
            if data_std > 1e-12:
                fft_result = np.fft.rfft(data_ac)
                power = np.abs(fft_result) ** 2
                total_power = float(np.sum(power[1:]))

                if total_power > 1e-20:
                    n_bins = len(power) - 1
                    high_freq_start = max(1, int(n_bins * 0.7))
                    high_freq_power = float(np.sum(power[high_freq_start:]))
                    high_freq_ratio = high_freq_power / total_power

                    if high_freq_ratio > 0.4:
                        state["warnings"].append(
                            f"Sample rate ({sample_rate:.3e} Hz) may be "
                            "insufficient - significant spectral energy "
                            "near Nyquist suggests undersampling"
                        )
                        state["suggestions"].append(
                            "Recommend increasing sample rate for accurate measurements"
                        )
                        state["expected_result"] = "unreliable"
                        state["confidence"] = min(state["confidence"], 0.5)


def _check_data_length_adequacy(
    trace: WaveformTrace,
    measurement_name: str,
    categories: dict[str, list[str]],
    classification: dict[str, Any],
    state: dict[str, Any],
) -> None:
    """Check if signal length is adequate for the measurement.

    Args:
        trace: Input waveform trace.
        measurement_name: Name of the measurement to check.
        categories: Dict mapping category names to measurement lists.
        classification: Signal classification info.
        state: Mutable dict with warnings, suggestions, expected_result,
            confidence.
    """
    n = len(trace.data)

    # Check spectral measurements
    if measurement_name in categories["spectral"]:
        if n < 256:
            state["warnings"].append(
                f"Signal length ({n} samples) may be too short for spectral analysis"
            )
            state["suggestions"].append(
                "Recommend at least 1024 samples for FFT-based measurements"
            )
            state["expected_result"] = "unreliable"
            state["confidence"] = min(state["confidence"], 0.5)

    # Check frequency measurements
    if measurement_name in categories["frequency"]:
        if classification["frequency_estimate"] is not None:
            min_samples = trace.metadata.sample_rate / classification["frequency_estimate"]
            if n < min_samples * 0.5:
                state["warnings"].append(
                    f"Signal length ({n} samples) captures < 0.5 "
                    "periods, frequency measurement may fail"
                )
                state["suggestions"].append(
                    "Capture at least 2 periods for reliable frequency measurement"
                )
                state["expected_result"] = "unreliable"
                state["confidence"] = min(state["confidence"], 0.5)
            elif n < min_samples * 2:
                state["suggestions"].append("Capture at least 10 periods for best accuracy")
                state["confidence"] = min(state["confidence"], 0.75)


def check_measurement_suitability(
    trace: WaveformTrace,
    measurement_name: str,
) -> dict[str, Any]:
    """Check if a measurement is suitable for this signal.

    Analyzes signal characteristics to determine if a specific measurement
    will produce valid results, and provides warnings and suggestions.

    Args:
        trace: Input waveform trace.
        measurement_name: Name of measurement to check (e.g., "frequency",
            "rise_time").

    Returns:
        Dictionary containing:
        - suitable: True if measurement is appropriate for this signal
        - confidence: Confidence in suitability assessment (0.0-1.0)
        - warnings: List of warning strings
        - suggestions: List of suggestion strings
        - expected_result: "valid", "nan", or "unreliable"

    Example:
        >>> trace = osc.load('dc_signal.wfm')
        >>> check = osc.check_measurement_suitability(trace, "frequency")
        >>> if not check['suitable']:
        ...     print(f"Warning: {check['warnings']}")
        Warning: ['Frequency measurement not suitable for DC signal']

    References:
        IEEE 181-2011: Measurement applicability
    """
    classification = classify_signal(trace)
    quality = assess_signal_quality(trace)

    state: dict[str, Any] = {
        "suitable": True,
        "confidence": 0.8,
        "expected_result": "valid",
        "warnings": [],
        "suggestions": [],
    }

    categories = _get_measurement_categories()
    signal_type = classification["type"]
    characteristics = classification["characteristics"]

    # Run all compatibility checks
    _check_dc_signal_compatibility(signal_type, measurement_name, categories, state)
    _check_aperiodic_signal_compatibility(characteristics, measurement_name, categories, state)
    _check_digital_signal_compatibility(signal_type, measurement_name, categories, state)
    _check_edge_count_requirements(trace, measurement_name, categories, classification, state)
    _check_quality_impacts(quality, measurement_name, categories, state)
    _check_sample_rate_adequacy(trace, measurement_name, categories, classification, state)
    _check_data_length_adequacy(trace, measurement_name, categories, classification, state)

    # Extract confidence (guaranteed to be float from initialization)
    confidence_value = float(state["confidence"])
    return {
        "suitable": state["suitable"],
        "confidence": confidence_value,
        "warnings": state["warnings"],
        "suggestions": state["suggestions"],
        "expected_result": state["expected_result"],
    }
