"""Base-adapter auto-TTS must pass a platform-aware explicit output path.

Regression tests for the cleared-contextvar bug (#57049, #36685): the
post-handler auto-TTS block in ``BasePlatformAdapter._process_message_background``
runs AFTER ``_clear_session_env`` wiped ``HERMES_SESSION_PLATFORM``, so the
TTS tool's contextvar-based ``want_opus`` detection always resolved False on
that path and Opus platforms received MP3 (audio attachment, not a native
voice bubble). The fix passes an explicit output path from
``build_auto_tts_output_path(platform)``, which consults the TTS tool's
``OPUS_VOICE_PLATFORMS`` set — the single source of truth.
"""

import asyncio
import json
from unittest.mock import AsyncMock, patch

import pytest

from gateway.config import Platform, PlatformConfig
from gateway.platforms.base import (
    BasePlatformAdapter,
    MessageEvent,
    MessageType,
    SendResult,
    build_auto_tts_output_path,
)
from gateway.session import SessionSource, build_session_key
from tools.tts_tool import OPUS_VOICE_PLATFORMS


class _DummyAdapter(BasePlatformAdapter):
    def __init__(self, platform: Platform):
        super().__init__(PlatformConfig(enabled=True, token="fake-token"), platform)
        self.sent = []

    async def connect(self, *, is_reconnect: bool = False) -> bool:
        return True

    async def disconnect(self) -> None:
        return None

    async def send(self, chat_id, content, reply_to=None, metadata=None) -> SendResult:
        self.sent.append({"chat_id": chat_id, "content": content})
        return SendResult(success=True, message_id="1")

    async def send_typing(self, chat_id: str, metadata=None) -> None:
        return None

    async def stop_typing(self, chat_id: str, metadata=None) -> None:
        return None

    async def get_chat_info(self, chat_id: str):
        return {"id": chat_id}


def _make_voice_event(platform: Platform) -> MessageEvent:
    return MessageEvent(
        text="hello",
        message_type=MessageType.VOICE,
        source=SessionSource(
            platform=platform,
            chat_id="-1001",
            chat_type="group",
        ),
        message_id="voice-1",
    )


def _hold_typing():
    async def hold(*_args, **_kwargs):
        await asyncio.Event().wait()

    return hold


# ---------------------------------------------------------------------------
# build_auto_tts_output_path: OPUS_VOICE_PLATFORMS is the single source of truth
# ---------------------------------------------------------------------------


@pytest.mark.parametrize(
    "platform", [Platform.DISCORD, Platform.SLACK, "irc", None]
)
def test_output_path_is_mp3_for_non_opus_platforms(platform):
    path = build_auto_tts_output_path(platform)
    assert path.endswith(".mp3"), path


# ---------------------------------------------------------------------------
# Base-adapter auto-TTS block: explicit output_path, no contextvar reliance
# ---------------------------------------------------------------------------

async def _run_auto_tts(adapter: _DummyAdapter, platform: Platform):
    adapter._keep_typing = _hold_typing()
    adapter._should_auto_tts_for_chat = lambda _chat_id: True
    adapter.play_tts = AsyncMock(return_value=SendResult(success=True, message_id="tts-1"))
    long_reply = "x" * 2000  # avoid the telegram caption-collapse path
    adapter.set_message_handler(lambda _event: asyncio.sleep(0, result=long_reply))
    event = _make_voice_event(platform)
    requested = []

    def fake_tts(*, text, output_path=None):
        requested.append(output_path)
        from pathlib import Path
        Path(output_path).parent.mkdir(parents=True, exist_ok=True)
        Path(output_path).write_bytes(b"fake audio")
        return json.dumps({"success": True, "file_path": output_path})

    with patch("tools.tts_tool.check_tts_requirements", return_value=True), patch(
        "tools.tts_tool.text_to_speech_tool", side_effect=fake_tts
    ):
        await adapter._process_message_background(
            event, build_session_key(event.source)
        )
    return requested, adapter


@pytest.mark.asyncio
async def test_base_auto_tts_skips_playback_when_tool_reports_failure():
    """A success=False tool result must not deliver a stale/partial file."""
    adapter = _DummyAdapter(Platform.TELEGRAM)
    adapter._keep_typing = _hold_typing()
    adapter._should_auto_tts_for_chat = lambda _chat_id: True
    adapter.play_tts = AsyncMock(return_value=SendResult(success=True, message_id="tts-1"))
    adapter.set_message_handler(lambda _event: asyncio.sleep(0, result="reply text"))
    event = _make_voice_event(Platform.TELEGRAM)

    def fake_tts(*, text, output_path=None):
        from pathlib import Path
        Path(output_path).parent.mkdir(parents=True, exist_ok=True)
        Path(output_path).write_bytes(b"partial")
        return json.dumps({"success": False, "error": "backend exploded"})

    with patch("tools.tts_tool.check_tts_requirements", return_value=True), patch(
        "tools.tts_tool.text_to_speech_tool", side_effect=fake_tts
    ):
        await adapter._process_message_background(
            event, build_session_key(event.source)
        )

    adapter.play_tts.assert_not_awaited()
    # Text reply still goes out.
    assert adapter.sent and adapter.sent[0]["content"] == "reply text"
