import asyncio
import base64
import json
import subprocess

import pytest

from device_gateway.speech import audio_to_pcm, CHUNK_BYTES
from device_gateway.voice import wav_pcm
from fake_device import DownlinkRecorder
from test_speech import manager


@pytest.mark.parametrize('bad_sequence', [0, 2])
def test_recorder_rejects_duplicate_or_missing_sequence(bad_sequence):
    recorder = DownlinkRecorder()
    recorder.receive(json.dumps(dict(type='speech_start', speech_id='s', sample_rate=44100, bits=16, channels=1)))
    chunk = dict(type='speech_chunk', speech_id='s', sequence=0, audio=base64.b64encode(b'\0\0').decode())
    recorder.receive(json.dumps(chunk))
    with pytest.raises(AssertionError, match='missing or duplicate'):
        recorder.receive(json.dumps({**chunk, 'sequence':bad_sequence}))


def test_compressed_mp3_to_pcm_is_real_ffmpeg_conversion():
    pcm = b'\0\x10\0\xf0'*22050
    compressed = subprocess.run(['ffmpeg', '-hide_banner', '-loglevel', 'error', '-f', 'wav', '-i', 'pipe:0',
                                '-f', 'mp3', '-codec:a', 'libmp3lame', 'pipe:1'], input=wav_pcm(pcm),
                               capture_output=True, check=True).stdout
    converted = asyncio.run(audio_to_pcm(compressed))
    # MP3 encoder padding is legal; output is bounded 44.1 kHz signed-16 mono.
    assert len(pcm) <= len(converted) < len(pcm)+8820
    assert len(converted) % 2 == 0 and any(converted)


def test_pacing_never_catches_up_in_bursts_after_slow_send(tmp_path):
    async def check():
        speech, device, sent, _, _ = manager(tmp_path, frames=4)
        original = speech.send
        async def delayed(device, payload):
            if payload['type'] == 'speech_chunk' and payload['sequence'] == 1:
                await asyncio.sleep(.2)
            await original(device, payload)
        speech.send = delayed
        speech.start(device, 'screen', 'Public text.')
        await asyncio.gather(*list(speech.tasks))
        chunks = [time for time, payload in sent if payload['type'] == 'speech_chunk']
        assert len(chunks) == 4
        assert chunks[1]-chunks[0] >= .2 + CHUNK_BYTES/88200 - .005
        assert all(b-a >= CHUNK_BYTES/88200-.005 for a, b in zip(chunks, chunks[1:]))
        assert sent[-1][1]['type'] == 'speech_end'
        await speech.close()
    asyncio.run(check())
