"""Phase C verification gate: LUFS-matched metric comparison vs genre profile.
The gate is the project's definition of done."""
import numpy as np
import pyloudnorm as pyln
from .reference_engine import (third_octave_spectrum_db, band_width_ratio,
                               THIRD_OCTAVE_HZ)
from .tp_limiter import true_peak_db


def _skipped(reason):
    return {"skipped": True, "reason": reason, "pass": True}


def _safe_lufs(audio, sr, meter=None):
    meter = meter or pyln.Meter(sr)
    if np.max(np.abs(audio)) < 1e-9 or len(audio) < int(0.4 * sr):
        return None, "silent_or_too_short"
    try:
        lufs = meter.integrated_loudness(audio)
        if not np.isfinite(lufs):
            return None, "non_finite_loudness"
        return float(lufs), None
    except Exception:
        return None, "loudness_error"


def _sibilance_ratio(audio, sr):
    from ..analyzer import AudioAnalyzer
    import librosa
    mono = librosa.to_mono(audio.T) if audio.ndim == 2 else audio
    return float(AudioAnalyzer().calculate_sibilance_ratio(mono, sr))


def _whistle_count(audio, sr):
    """Count whistles via analyzer resonance path (in-memory, no full analyze).

    Parity with ``analyzer.py`` ~478-534: STFT mean spectrum, find_peaks in
    200-10 kHz with prominence>=4, top-3 by prominence, count those > 10.0.
    """
    import librosa
    from scipy.signal import find_peaks

    y_mono = librosa.to_mono(audio.T) if audio.ndim == 2 else audio
    S = np.abs(librosa.stft(y_mono))
    freqs = librosa.fft_frequencies(sr=sr)
    mean_spec = np.mean(S, axis=1)
    log_spec_full = 20 * np.log10(mean_spec + 1e-9)
    res_idx = np.where((freqs > 200) & (freqs < 10000))[0]
    peaks, props = find_peaks(log_spec_full[res_idx], prominence=4, width=1)
    if len(peaks) == 0:
        return 0
    peak_proms = props["prominences"]
    sorted_idx = np.argsort(peak_proms)[::-1]
    return sum(1 for i in sorted_idx[:3] if peak_proms[i] > 10.0)


def _overall_pass(checks):
    for c in checks.values():
        if c.get("skipped"):
            continue
        if not c.get("pass", False):
            return False
    return True


def verify_master(audio, sr, profile, input_audio=None, separation_quality=None):
    """Phase C verification gate: LUFS-matched metric comparison vs genre profile.

    ``artifact_score`` pass criterion: ``whistle_count(master) <= whistle_count(input)``
    only. ``separation_quality`` is recorded in the check detail; ``degraded`` adds a
    ``warning`` field but does not fail the check (light mode is the designed
    mitigation for poor stem separation).
    """
    meter = pyln.Meter(sr)
    lufs, lufs_err = _safe_lufs(audio, sr, meter)
    checks = {}
    norm = None
    spec = None
    hz = np.array(THIRD_OCTAVE_HZ)

    if lufs_err:
        loud = {"pass": False, "reason": lufs_err, "value": None}
        checks["loudness"] = loud
    else:
        norm = audio * 10 ** ((-14.0 - lufs) / 20.0)
        spec = np.array(third_octave_spectrum_db(norm, sr))

    if "third_octave_db" not in profile:
        checks["spectral_distance_db"] = _skipped("no third_octave_db in profile")
    elif lufs_err:
        checks["spectral_distance_db"] = {"pass": False, "reason": lufs_err, "value": None}
    else:
        ref_spec = np.array(profile["third_octave_db"])
        sel = (hz >= 50) & (hz <= 16000)
        dev = np.abs(spec[sel] - ref_spec[sel])
        checks["spectral_distance_db"] = {
            "value": float(np.mean(dev)),
            "max_band": float(np.max(dev)),
            "pass": bool(np.mean(dev) <= 2.5 and np.max(dev) <= 5.0)}

    try:
        tp = true_peak_db(audio, sr)
        checks["true_peak_dbtp"] = {
            "value": float(tp) if np.isfinite(tp) else None,
            "pass": bool(np.isfinite(tp) and tp <= -0.9)}
        if not np.isfinite(tp):
            checks["true_peak_dbtp"]["reason"] = "non_finite_true_peak"
    except Exception as exc:
        checks["true_peak_dbtp"] = {"pass": False, "reason": str(exc), "value": None}

    if "lufs" not in profile:
        checks["lufs_delta"] = _skipped("no lufs in profile")
    elif lufs_err:
        checks["lufs_delta"] = {"pass": False, "reason": lufs_err, "value": None}
    else:
        d = lufs - float(profile["lufs"])
        checks["lufs_delta"] = {"value": float(d), "pass": bool(abs(d) <= 1.0)}

    if "plr" not in profile:
        checks["plr_delta"] = _skipped("no plr in profile")
    elif lufs_err:
        checks["plr_delta"] = {"pass": False, "reason": lufs_err, "value": None}
    else:
        peak_db = 20 * np.log10(np.max(np.abs(audio)) + 1e-12)
        plr_d = (peak_db - lufs) - float(profile["plr"])
        checks["plr_delta"] = {
            "value": float(plr_d) if np.isfinite(plr_d) else None,
            "pass": bool(np.isfinite(plr_d) and abs(plr_d) <= 2.0)}

    if audio.ndim == 2 and audio.shape[1] == 1:
        checks["width_dev"] = _skipped("mono")
    elif "band_width_ratio" not in profile:
        checks["width_dev"] = _skipped("no band_width_ratio in profile")
    else:
        w = band_width_ratio(audio, sr)
        ref_w = profile["band_width_ratio"]
        devs = {k: abs(w[k] - ref_w[k]) for k in w}
        ok = all(devs[k] <= max(0.25 * ref_w[k], 0.05) for k in devs)
        checks["width_dev"] = {"value": devs, "pass": bool(ok)}

    if "third_octave_db" not in profile:
        checks["hf_sharpness_db"] = _skipped("no third_octave_db in profile")
    elif lufs_err:
        checks["hf_sharpness_db"] = {"pass": False, "reason": lufs_err, "value": None}
    else:
        ref_spec = np.array(profile["third_octave_db"])
        cand_hf = float(np.mean(spec[(hz >= 10000) & (hz <= 16000)]))
        ref_hf = float(np.mean(ref_spec[(hz >= 10000) & (hz <= 16000)]))
        checks["hf_sharpness_db"] = {
            "value": cand_hf - ref_hf,
            "pass": bool(abs(cand_hf - ref_hf) <= 3.0)}

    if "sibilance_ratio" not in profile:
        checks["sibilance_ratio"] = _skipped("no sibilance_ratio in profile")
    elif lufs_err:
        checks["sibilance_ratio"] = {"pass": False, "reason": lufs_err, "value": None}
    else:
        sib = _sibilance_ratio(norm, sr)
        ref_sib = float(profile["sibilance_ratio"])
        checks["sibilance_ratio"] = {
            "value": float(sib),
            "pass": bool(sib <= ref_sib * 1.2)}

    if input_audio is not None:
        master_w = _whistle_count(audio, sr)
        input_w = _whistle_count(input_audio, sr)
        art = {
            "value": {
                "whistle_count_master": master_w,
                "whistle_count_input": input_w,
                "separation_quality": separation_quality,
            },
            "pass": bool(master_w <= input_w),
        }
        if separation_quality == "degraded":
            art["warning"] = (
                "separation_quality degraded (light mode applied; not a gate fail)")
        checks["artifact_score"] = art
    else:
        checks["artifact_score"] = _skipped("no input_audio")

    return {"pass": _overall_pass(checks), "checks": checks}


def export_ab_snippet(master, reference_audio, sr, out_path, seconds=20):
    """Loudness-matched interleaved A/B: 5 s ref / 5 s master alternating."""
    import soundfile as sf
    meter = pyln.Meter(sr)

    def norm(x):
        lufs, err = _safe_lufs(x, sr, meter)
        if err:
            return x
        return x * 10 ** ((-14.0 - lufs) / 20.0)

    m, r = norm(master), norm(reference_audio)
    seg = sr * 5
    n = min(len(m), len(r), sr * seconds)
    start = max(0, (min(len(m), len(r)) - n) // 2)
    chunks = []
    for i in range(start, start + n, seg):
        chunks.append(r[i:i + seg])
        chunks.append(m[i:i + seg])
    sf.write(out_path, np.concatenate(chunks)[: sr * seconds * 2], sr)
