"""Unit tests for the bridge STT speech gate.

The gate decides whether a Whisper segment is real human speech or just noise /
a brief loud blip that Whisper hallucinated text from. Only speech should reach
the reply engine, so a noisy mic that momentarily opens without anyone speaking
produces no transcript and no reply. Thresholds are config-driven, so the tests
pass explicit references rather than hardcoding the production defaults.
"""

import numpy as np
import pytest

from bridge.stt_filter import (
    filter_speech_segments,
    has_speech,
    is_non_speech,
    segment_confidence,
)


@pytest.mark.unit
def test_has_speech_rejects_silence():
    # Pure digital silence is unambiguously non-speech: VAD must skip STT.
    silence = np.zeros(16000, dtype=np.float32)  # 1s @ 16kHz
    assert has_speech(silence, 16000) is False


@pytest.mark.unit
def test_has_speech_rejects_a_brief_loud_blip():
    # A short loud transient (a clap / pop): mostly silence with a 50ms spike,
    # below the min-speech duration, so no real speech region is found.
    audio = np.zeros(16000, dtype=np.float32)
    audio[8000:8800] = 0.9  # ~50ms full-scale burst
    assert has_speech(audio, 16000, min_speech_duration_ms=200) is False


@pytest.mark.unit
def test_has_speech_fails_open_on_empty_when_vad_present_returns_false():
    # Empty audio has nothing to transcribe; treat as non-speech.
    assert has_speech(np.zeros(0, dtype=np.float32), 16000) is False


class Seg:
    """Minimal stand-in for a faster-whisper segment."""

    def __init__(self, text, no_speech_prob=0.0, avg_logprob=0.0):
        self.text = text
        self.no_speech_prob = no_speech_prob
        self.avg_logprob = avg_logprob


@pytest.mark.unit
def test_real_speech_is_kept():
    seg = Seg("오늘 일정 알려줘", no_speech_prob=0.02, avg_logprob=-0.2)
    assert filter_speech_segments(
        [seg], no_speech_threshold=0.5, min_confidence=0.3
    ) == [seg]


@pytest.mark.unit
def test_noise_with_high_no_speech_prob_is_dropped():
    # A mic blip Whisper hallucinated "감사합니다" from: not speech.
    seg = Seg("감사합니다", no_speech_prob=0.92, avg_logprob=0.5)
    assert filter_speech_segments(
        [seg], no_speech_threshold=0.5, min_confidence=0.3
    ) == []


@pytest.mark.unit
def test_no_speech_cutoff_runs_before_the_confidence_check():
    # Confident hallucination: high avg_logprob but also high no_speech_prob.
    # The no-speech cutoff must catch it regardless of confidence.
    seg = Seg("MBC 뉴스", no_speech_prob=0.8, avg_logprob=0.9)
    assert filter_speech_segments(
        [seg], no_speech_threshold=0.5, min_confidence=0.3
    ) == []


@pytest.mark.unit
def test_low_confidence_decode_is_dropped():
    # avg_logprob -0.8 -> confidence 0.2, below the 0.3 floor.
    seg = Seg("어버버", no_speech_prob=0.1, avg_logprob=-0.8)
    assert filter_speech_segments(
        [seg], no_speech_threshold=0.5, min_confidence=0.3
    ) == []


@pytest.mark.unit
def test_order_preserved_dropping_only_non_speech():
    a = Seg("진짜 말한 문장", no_speech_prob=0.05, avg_logprob=-0.1)
    noise = Seg("음", no_speech_prob=0.95, avg_logprob=0.4)
    b = Seg("두 번째 문장", no_speech_prob=0.05, avg_logprob=-0.1)
    kept = filter_speech_segments(
        [a, noise, b], no_speech_threshold=0.5, min_confidence=0.3
    )
    assert kept == [a, b]


@pytest.mark.unit
def test_segments_missing_metadata_are_kept():
    # No no_speech_prob / avg_logprob -> we can't prove it's noise, so keep it.
    class Bare:
        text = "메타데이터 없는 문장"

    seg = Bare()
    assert filter_speech_segments(
        [seg], no_speech_threshold=0.5, min_confidence=0.3
    ) == [seg]


@pytest.mark.unit
def test_is_non_speech_uses_an_inclusive_threshold():
    assert is_non_speech(0.5, 0.5) is True
    assert is_non_speech(0.49, 0.5) is False


@pytest.mark.unit
def test_segment_confidence_prefers_avg_logprob():
    assert segment_confidence(Seg("x", avg_logprob=-0.2)) == pytest.approx(0.8)
    # Falls back to 1 - no_speech_prob when avg_logprob is absent.

    class NoLogprob:
        text = "x"
        no_speech_prob = 0.25

    assert segment_confidence(NoLogprob()) == pytest.approx(0.75)
