"""SAE J211 Channel Frequency Class (CFC) filtering.

Implements the standard CFC filter as a 4th-order Butterworth low-pass
applied forward-reverse (zero-phase) per SAE J211-1.

CFC class to -3dB cutoff: f_c = CFC * (5/3)
    CFC 60  ->  100 Hz
    CFC 180 ->  300 Hz
    CFC 600 -> 1000 Hz
    CFC 1000-> 1650 Hz
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

import numpy as np
from scipy.signal import butter, filtfilt

from impakt.channel.lookup import CFC_CLASSES, CFC_MINIMUM_SAMPLE_RATES
from impakt.channel.model import Channel


VALID_CFC_CLASSES = frozenset(CFC_CLASSES.keys())


@dataclass(frozen=True)
class CFCFilter:
    """SAE J211 CFC filter transform.

    Args:
        cfc_class: CFC class number (60, 180, 600, or 1000).
    """

    cfc_class: int

    def __post_init__(self) -> None:
        if self.cfc_class not in VALID_CFC_CLASSES:
            raise ValueError(
                f"Invalid CFC class: {self.cfc_class}. Valid classes: {sorted(VALID_CFC_CLASSES)}"
            )

    @property
    def name(self) -> str:
        return "cfc_filter"

    @property
    def params(self) -> dict[str, Any]:
        return {"cfc_class": self.cfc_class}

    @property
    def cutoff_hz(self) -> float:
        """The -3dB cutoff frequency in Hz."""
        return CFC_CLASSES[self.cfc_class]

    def apply(self, channel: Channel) -> Channel:
        """Apply CFC filter to a channel.

        Raises:
            ValueError: If sample rate is too low for the CFC class.
        """
        cutoff = self.cutoff_hz
        nyquist = channel.sample_rate / 2.0

        if cutoff >= nyquist:
            raise ValueError(
                f"CFC {self.cfc_class} cutoff ({cutoff} Hz) >= Nyquist "
                f"({nyquist} Hz). Sample rate {channel.sample_rate} Hz is "
                f"too low. Minimum: {CFC_MINIMUM_SAMPLE_RATES.get(self.cfc_class, 'unknown')} Hz."
            )

        # Normalized cutoff (0 to 1 where 1 = Nyquist)
        wn = cutoff / nyquist

        # 4th-order Butterworth coefficients
        b, a = butter(N=4, Wn=wn, btype="low")

        # Zero-phase forward-reverse filtering (effective 8th order)
        filtered = filtfilt(
            b, a, channel.data, padlen=min(3 * max(len(a), len(b)), len(channel.data) - 1)
        )

        return channel.with_data(
            data=filtered,
            cfc_class=self.cfc_class,
            transform_note=f"CFC {self.cfc_class} ({cutoff:.0f} Hz)",
        )


def cfc_filter(channel: Channel, cfc_class: int) -> Channel:
    """Convenience function to apply a CFC filter.

    Args:
        channel: Input channel.
        cfc_class: CFC class (60, 180, 600, or 1000).

    Returns:
        New filtered channel.
    """
    return CFCFilter(cfc_class=cfc_class).apply(channel)
