"""WS /v1/realtime: auth gate + a minimal commit flow."""
import json

import numpy as np


def _pcm16(seconds=1.0, rate=16000):
    return (np.zeros(int(seconds * rate), dtype="<i2")).tobytes()


def test_rejects_without_token(client):
    from starlette.websockets import WebSocketDisconnect
    import pytest
    with pytest.raises(WebSocketDisconnect):
        with client.websocket_connect("/v1/realtime") as ws:
            ws.receive_text()


def test_accepts_with_query_token_and_commits(client):
    with client.websocket_connect("/v1/realtime?token=testkey&language=en") as ws:
        ws.send_bytes(_pcm16(1.0))
        ws.send_text(json.dumps({"type": "input_audio_buffer.commit"}))
        # Expect at least a completed event (delta may or may not precede it).
        saw_completed = False
        for _ in range(6):
            msg = json.loads(ws.receive_text())
            if msg["type"] == "conversation.item.input_audio_transcription.completed":
                saw_completed = True
                break
        assert saw_completed


def test_not_ready_closes(client, fake_engine):
    fake_engine.service.is_ready = False
    with client.websocket_connect("/v1/realtime?token=testkey") as ws:
        msg = json.loads(ws.receive_text())
        assert msg["type"] == "error"


def test_eviction_never_drops_past_anchor():
    """Regression: _evict_committed must never evict audio at/after the
    committer anchor (that audio is unprocessed — dropping it silently loses
    speech). Reproduces the lagging-inference case."""
    import sys
    from pathlib import Path
    sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "src"))
    sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
    from app.realtime import RealtimeSession, SAMPLE_RATE, _ROLLING_KEEP_SECONDS

    class _Eng:
        class service:  # noqa: N801
            @staticmethod
            def transcribe_window_sync(a, l): return []
    s = RealtimeSession(_Eng(), "en")
    # 60s streamed in 1s chunks; inference lagging → anchor only at 5s.
    for _ in range(60):
        s._chunks.append(np.zeros(SAMPLE_RATE, dtype="<i2").astype(np.float32))
        s._chunk_samples += SAMPLE_RATE
    s._committer.anchor_samples = 5 * SAMPLE_RATE   # far behind the 60s head
    s._evict_committed()
    # Nothing at/after the 5s anchor may have been evicted.
    assert s._base <= 5 * SAMPLE_RATE
    # And the kept buffer still starts no later than the anchor.
    assert s._base + s._chunk_samples == 60 * SAMPLE_RATE  # head unchanged


def test_eviction_bounds_tail_when_anchor_recent():
    import sys
    from pathlib import Path
    sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "src"))
    sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
    from app.realtime import RealtimeSession, SAMPLE_RATE, _ROLLING_KEEP_SECONDS

    class _Eng:
        class service:  # noqa: N801
            @staticmethod
            def transcribe_window_sync(a, l): return []
    s = RealtimeSession(_Eng(), "en")
    for _ in range(60):
        s._chunks.append(np.zeros(SAMPLE_RATE, dtype="<i2").astype(np.float32))
        s._chunk_samples += SAMPLE_RATE
    s._committer.anchor_samples = 55 * SAMPLE_RATE  # recent (within window)
    s._evict_committed()
    # Rolling tail keeps ~_ROLLING_KEEP_SECONDS, never more.
    assert s._chunk_samples <= (_ROLLING_KEEP_SECONDS + 1) * SAMPLE_RATE
