"""Basic signal analyzer for AI MusiMuse.

This module defines :class:`BasicSignalAnalyzer`, the first real audio
analyzer.  It extracts fundamental signal statistics using NumPy
vectorized operations.

No spectral analysis is performed.  No Python loops over samples.
The analyzer must not perform logging — the pipeline owns all logging.
"""

from __future__ import annotations

import time

import numpy as np

from analyzer.analyzer import Analyzer
from analyzer.context import AnalysisContext
from analyzer.feature import Feature, FeatureSet
from analyzer.result import AnalysisResult
from core.exceptions import AnalysisError


def _dbfs(value: float) -> float:
    """Convert a linear amplitude value to dBFS.

    Args:
        value: Linear amplitude (>= 0).

    Returns:
        dBFS value, or ``-inf`` for zero amplitude.
    """
    if value <= 0.0:
        return float("-inf")
    return 20.0 * np.log10(value)


class BasicSignalAnalyzer(Analyzer):
    """Extracts fundamental signal statistics from decoded audio.

    Produces 8 features for mono audio and 11 for stereo, including
    peak amplitude, RMS, dBFS conversions, DC offset, and silence
    ratio.

    All calculations use NumPy vectorized operations.
    """

    name = "basic_signal"
    version = "1.0.0"

    def analyze(self, context: AnalysisContext) -> AnalysisResult:
        """Analyze decoded audio and return signal statistics.

        Args:
            context: The analysis context containing decoded audio.

        Returns:
            An :class:`AnalysisResult` with signal feature data.

        Raises:
            AnalysisError: If the audio is empty, contains NaN, or
                contains infinite values.
        """
        start = time.perf_counter()

        audio = context.decoded_audio
        samples = audio.samples

        if samples.size == 0:
            raise AnalysisError("Audio contains no samples")

        if np.any(np.isnan(samples)):
            raise AnalysisError("Audio contains NaN values")

        if np.any(np.isinf(samples)):
            raise AnalysisError("Audio contains infinite values")

        n_channels = audio.channels
        features: list[Feature] = []

        # Metadata features
        features.append(
            Feature(
                name="signal.duration_seconds",
                value=audio.duration,
                unit="s",
                analyzer=self.name,
                version=self.version,
            )
        )
        features.append(
            Feature(
                name="signal.sample_rate",
                value=audio.sample_rate,
                unit="Hz",
                analyzer=self.name,
                version=self.version,
            )
        )
        features.append(
            Feature(
                name="signal.channels",
                value=audio.channels,
                analyzer=self.name,
                version=self.version,
            )
        )

        # Per-channel analysis
        if n_channels == 1:
            mono = samples[:, 0]
            peak = float(np.max(np.abs(mono)))
            rms = float(np.sqrt(np.mean(mono**2)))
            dc_offset = float(np.mean(mono))
            silence_ratio = float(np.mean(np.abs(mono) < 0.001))

            features.append(
                Feature(
                    name="signal.peak",
                    value=peak,
                    analyzer=self.name,
                    version=self.version,
                )
            )
            features.append(
                Feature(
                    name="signal.rms",
                    value=rms,
                    analyzer=self.name,
                    version=self.version,
                )
            )
            features.append(
                Feature(
                    name="signal.peak_db",
                    value=_dbfs(peak),
                    unit="dBFS",
                    analyzer=self.name,
                    version=self.version,
                )
            )
            features.append(
                Feature(
                    name="signal.rms_db",
                    value=_dbfs(rms),
                    unit="dBFS",
                    analyzer=self.name,
                    version=self.version,
                )
            )
            features.append(
                Feature(
                    name="signal.dc_offset",
                    value=dc_offset,
                    analyzer=self.name,
                    version=self.version,
                )
            )
            features.append(
                Feature(
                    name="signal.silence_ratio",
                    value=silence_ratio,
                    analyzer=self.name,
                    version=self.version,
                )
            )
        else:
            left = samples[:, 0]
            right = samples[:, 1]

            peak_left = float(np.max(np.abs(left)))
            peak_right = float(np.max(np.abs(right)))
            rms_left = float(np.sqrt(np.mean(left**2)))
            rms_right = float(np.sqrt(np.mean(right**2)))

            all_samples = samples.flatten()
            peak = max(peak_left, peak_right)
            rms = float(np.sqrt(np.mean(all_samples**2)))
            dc_offset = float(np.mean(all_samples))
            silence_ratio = float(np.mean(np.abs(all_samples) < 0.001))

            features.append(
                Feature(
                    name="signal.peak_left",
                    value=peak_left,
                    analyzer=self.name,
                    version=self.version,
                )
            )
            features.append(
                Feature(
                    name="signal.peak_right",
                    value=peak_right,
                    analyzer=self.name,
                    version=self.version,
                )
            )
            features.append(
                Feature(
                    name="signal.peak_db",
                    value=_dbfs(peak),
                    unit="dBFS",
                    analyzer=self.name,
                    version=self.version,
                )
            )
            features.append(
                Feature(
                    name="signal.rms_left",
                    value=rms_left,
                    analyzer=self.name,
                    version=self.version,
                )
            )
            features.append(
                Feature(
                    name="signal.rms_right",
                    value=rms_right,
                    analyzer=self.name,
                    version=self.version,
                )
            )
            features.append(
                Feature(
                    name="signal.rms_db",
                    value=_dbfs(rms),
                    unit="dBFS",
                    analyzer=self.name,
                    version=self.version,
                )
            )
            features.append(
                Feature(
                    name="signal.dc_offset",
                    value=dc_offset,
                    analyzer=self.name,
                    version=self.version,
                )
            )
            features.append(
                Feature(
                    name="signal.silence_ratio",
                    value=silence_ratio,
                    analyzer=self.name,
                    version=self.version,
                )
            )

        feature_set = FeatureSet(features)
        elapsed_ms = (time.perf_counter() - start) * 1000.0

        return AnalysisResult(
            analyzer_name=self.name,
            analyzer_version=self.version,
            execution_time_ms=elapsed_ms,
            success=True,
            warnings=(),
            feature_set=feature_set,
        )
