import json

from app.services.voice.event_parser import VoiceClientEventParser
from app.services.voice.events import (
    AbortEvent,
    AudioFrameEvent,
    FinalizeEvent,
    IotStateEvent,
    ListenEvent,
    McpMessageEvent,
    NoopEvent,
    TextPromptEvent,
)
from app.services.voice.opus_transport import PROTOCOL_V2_HEADER_SIZE


def _text_msg(payload):
    return {"type": "websocket.receive", "text": json.dumps(payload)}


def test_parser_strips_v2_binary_header():
    parser = VoiceClientEventParser()
    event = parser.parse(
        {
            "type": "websocket.receive",
            "bytes": b"x" * PROTOCOL_V2_HEADER_SIZE + b"opus",
        },
        protocol_version=2,
    )
    assert isinstance(event, AudioFrameEvent)
    assert event.opus_payload == b"opus"


def test_parser_text_control_messages():
    parser = VoiceClientEventParser()

    assert isinstance(
        parser.parse(_text_msg({"type": "finalize"}), protocol_version=1), FinalizeEvent
    )
    assert isinstance(
        parser.parse(
            _text_msg({"type": "listen", "state": "start"}), protocol_version=1
        ),
        ListenEvent,
    )
    assert isinstance(
        parser.parse(_text_msg({"type": "abort", "reason": "tap"}), protocol_version=1),
        AbortEvent,
    )
    assert isinstance(
        parser.parse(_text_msg({"type": "text", "text": "hello"}), protocol_version=1),
        TextPromptEvent,
    )


def test_parser_iot_and_mcp_messages():
    parser = VoiceClientEventParser()
    iot = parser.parse(
        _text_msg({"type": "iot", "descriptors": [{"name": "lamp"}], "states": []}),
        protocol_version=1,
    )
    assert isinstance(iot, IotStateEvent)
    assert iot.descriptors == [{"name": "lamp"}]

    mcp = parser.parse(
        _text_msg(
            {
                "type": "mcp",
                "payload": {
                    "method": "notifications/device_event",
                    "params": {"event": "pat"},
                },
            }
        ),
        protocol_version=1,
    )
    assert isinstance(mcp, McpMessageEvent)
    assert mcp.method == "notifications/device_event"


def test_parser_invalid_json_is_noop():
    parser = VoiceClientEventParser()
    event = parser.parse(
        {"type": "websocket.receive", "text": "not-json"}, protocol_version=1
    )
    assert isinstance(event, NoopEvent)


def test_parser_listen_states_roundtrip():
    parser = VoiceClientEventParser()

    detect = parser.parse(
        _text_msg({"type": "listen", "state": "detect", "text": "xin chao"}),
        protocol_version=1,
    )
    assert isinstance(detect, ListenEvent)
    assert detect.state == "detect" and detect.text == "xin chao"

    stop = parser.parse(
        _text_msg({"type": "listen", "state": "stop"}), protocol_version=1
    )
    assert isinstance(stop, ListenEvent)
    assert stop.state == "stop"


async def test_soniox_finalize_skips_duplicate_without_new_audio():
    import asyncio
    import sys
    import types

    soniox_module = types.ModuleType("soniox")
    soniox_module.AsyncSonioxClient = object
    soniox_types_module = types.ModuleType("soniox.types")
    soniox_types_module.RealtimeSTTConfig = object
    opuslib_module = types.ModuleType("opuslib_next")
    opuslib_module.Decoder = object
    opuslib_module.OpusError = Exception
    sys.modules.setdefault("soniox", soniox_module)
    sys.modules.setdefault("soniox.types", soniox_types_module)
    sys.modules.setdefault("opuslib_next", opuslib_module)

    from app.services.asr.soniox import SonioxRTClient

    class FakeSession:
        def __init__(self):
            self.finalize_calls = 0

        async def finalize(self):
            self.finalize_calls += 1

    client = SonioxRTClient.__new__(SonioxRTClient)
    client._connected = True
    client._session = FakeSession()
    client._session_send_lock = asyncio.Lock()
    client._turn_finalize_requested_at = None
    client._turn_finalize_reason = None
    client._turn_finalize_requests = 0
    client._turn_packets_sent = 3
    client._turn_last_finalized_packet_count = 0
    client._turn_first_audio_at = None
    client._turn_last_audio_at = None
    client._turn_pcm_bytes_sent = 0

    await client.finalize(reason="listen_stop")
    await client.finalize(reason="vad_silence_timeout")
    client._turn_packets_sent = 4
    await client.finalize(reason="listen_stop")

    assert client._session.finalize_calls == 2
    assert client._turn_finalize_requests == 2
