"""Echo-reply text mirror: VoiceSession.on_segment_done forwards Claude's
reply back into the originating text channel, chunked to Discord's 2000-char
limit, gated on mirror_enabled, and resilient to send failures.

The pipeline calls router.route_message via the injected
`router_route_message` seam so tests can drive the reply text without
monkey-patching modules or invoking the real Claude subprocess.
"""
from __future__ import annotations

import asyncio
from unittest.mock import AsyncMock, MagicMock

import pytest

from src.voice.pipeline import VoiceSession


def _make_text_channel(send_mock: AsyncMock) -> MagicMock:
    tc = MagicMock(name="text_channel")
    tc.send = send_mock
    return tc


def _make_session(
    *,
    reply_text: str,
    text_channel,
    mirror_enabled: bool = True,
) -> VoiceSession:
    bot = MagicMock(name="bot")
    bot.get_channel = MagicMock(return_value=text_channel)
    bot.get_user = MagicMock(return_value=None)
    ttsq = MagicMock(name="ttsq")
    ttsq.push_text = MagicMock()
    ttsq.clear = MagicMock()
    route_mock = MagicMock(name="route_message", return_value=(reply_text, False))
    return VoiceSession(
        text_channel_id=1001,
        voice_channel_id=2002,
        guild_id=42,
        voice_client=MagicMock(name="voice_client"),
        bot=bot,
        ttsq=ttsq,
        whitelist=set(),
        record_enabled=False,
        mirror_enabled=mirror_enabled,
        transcripts_jsonl_path=None,
        loop=asyncio.get_event_loop_policy().new_event_loop(),
        router_route_message=route_mock,
    )


def _reply_chunks(send_mock: AsyncMock) -> list[str]:
    # Drop the user-mirror call (starts with the 🎤 microphone emoji); the
    # rest are reply chunks.
    return [
        call.args[0]
        for call in send_mock.call_args_list
        if not call.args[0].startswith("\U0001f3a4")
    ]


@pytest.mark.asyncio
async def test_long_reply_splits_into_multiple_chunks():
    long_reply = "răspuns lung " * 200  # ~2600 chars → ≥2 chunks at 2000-char limit
    send_mock = AsyncMock(name="text_send")
    text_channel = _make_text_channel(send_mock)
    session = _make_session(reply_text=long_reply, text_channel=text_channel)

    await session.on_segment_done(speaker_id=123, text="salut", no_speech_prob=0.1)

    chunks = _reply_chunks(send_mock)
    assert len(chunks) >= 2
    assert "".join(chunks).replace("\n", "").strip().startswith("răspuns lung")


@pytest.mark.asyncio
async def test_empty_reply_emits_no_reply_chunks():
    send_mock = AsyncMock(name="text_send")
    text_channel = _make_text_channel(send_mock)
    session = _make_session(reply_text="", text_channel=text_channel)

    await session.on_segment_done(speaker_id=123, text="salut", no_speech_prob=0.1)

    assert _reply_chunks(send_mock) == []


@pytest.mark.asyncio
async def test_whitespace_only_reply_emits_no_reply_chunks():
    send_mock = AsyncMock(name="text_send")
    text_channel = _make_text_channel(send_mock)
    session = _make_session(reply_text="   \n\t  ", text_channel=text_channel)

    await session.on_segment_done(speaker_id=123, text="salut", no_speech_prob=0.1)

    assert _reply_chunks(send_mock) == []


@pytest.mark.asyncio
async def test_mirror_disabled_sends_nothing():
    send_mock = AsyncMock(name="text_send")
    text_channel = _make_text_channel(send_mock)
    session = _make_session(
        reply_text="orice răspuns", text_channel=text_channel, mirror_enabled=False,
    )

    await session.on_segment_done(speaker_id=123, text="salut", no_speech_prob=0.1)

    assert send_mock.call_count == 0


@pytest.mark.asyncio
async def test_send_failure_is_swallowed(caplog):
    send_mock = AsyncMock(name="text_send", side_effect=RuntimeError("discord 500"))
    text_channel = _make_text_channel(send_mock)
    session = _make_session(reply_text="răspuns scurt", text_channel=text_channel)

    with caplog.at_level("WARNING"):
        # Must not raise — both user-mirror and reply-mirror trap exceptions.
        await session.on_segment_done(speaker_id=123, text="salut", no_speech_prob=0.1)

    # At least one warning was logged for a mirror send failure.
    assert any("mirror" in rec.message.lower() for rec in caplog.records)
