"""Tests for the OpenAI TTS `instructions` field passthrough.

Covers #14196: forwarding the OpenAI-spec `instructions` parameter through the
`text_to_speech` tool so the agent can control tone/emotion/pacing on
gpt-4o-mini-tts and OpenAI-compatible voice-design servers.
"""

import json
from unittest.mock import MagicMock, patch

import pytest


@pytest.fixture(autouse=True)
def clean_env(monkeypatch):
    for key in ("OPENAI_API_KEY", "HERMES_SESSION_PLATFORM"):
        monkeypatch.delenv(key, raising=False)


# ---------------------------------------------------------------------------
# Backend-level passthrough (_generate_openai_tts)
# ---------------------------------------------------------------------------

class TestOpenaiBackendInstructions:
    def _run(self, tmp_path, monkeypatch, *, tts_config=None, instructions=None):
        monkeypatch.setenv("OPENAI_API_KEY", "test-key")
        mock_client = MagicMock()
        mock_client.audio.speech.create.return_value = MagicMock()
        mock_cls = MagicMock(return_value=mock_client)

        with patch("tools.tts_tool._import_openai_client", return_value=mock_cls), \
             patch("tools.tts_tool._resolve_openai_audio_client_config",
                   return_value=("test-key", None, False)):
            from tools.tts_tool import _generate_openai_tts
            kwargs = {}
            if instructions is not None:
                kwargs["instructions"] = instructions
            _generate_openai_tts(
                "Hello", str(tmp_path / "out.mp3"), tts_config or {}, **kwargs
            )
        return mock_client.audio.speech.create

    def test_instructions_forwarded_when_provided(self, tmp_path, monkeypatch):
        """Tool arg `instructions` is passed to audio.speech.create as-is."""
        create = self._run(tmp_path, monkeypatch, instructions="Speak cheerfully.")
        assert create.call_args[1]["instructions"] == "Speak cheerfully."


    def test_empty_string_instructions_omitted(self, tmp_path, monkeypatch):
        """Empty string is treated as absent (not forwarded)."""
        create = self._run(tmp_path, monkeypatch, instructions="")
        assert "instructions" not in create.call_args[1]


# ---------------------------------------------------------------------------
# Tool-level plumbing (text_to_speech_tool -> _generate_openai_tts)
# ---------------------------------------------------------------------------

class TestToolLevelInstructions:
    def _invoke_tool(self, tmp_path, monkeypatch, *, instructions=None):
        monkeypatch.setenv("OPENAI_API_KEY", "test-key")

        mock_client = MagicMock()

        def fake_stream(path):
            # Mimic OpenAI SDK's stream_to_file by writing a tiny payload.
            with open(path, "wb") as f:
                f.write(b"ID3\x03\x00\x00\x00\x00\x00\x00")

        response = MagicMock()
        response.stream_to_file.side_effect = fake_stream
        mock_client.audio.speech.create.return_value = response

        mock_cls = MagicMock(return_value=mock_client)

        with patch("tools.tts_tool._import_openai_client", return_value=mock_cls), \
             patch("tools.tts_tool._resolve_openai_audio_client_config",
                   return_value=("test-key", None, False)), \
             patch("tools.tts_tool._load_tts_config",
                   return_value={"provider": "openai"}):
            from tools.tts_tool import text_to_speech_tool
            kwargs = {"output_path": str(tmp_path / "out.mp3")}
            if instructions is not None:
                kwargs["instructions"] = instructions
            result = text_to_speech_tool("Hello world", **kwargs)
        return mock_client.audio.speech.create, json.loads(result)

    def test_tool_threads_instructions_to_openai_create(
        self, tmp_path, monkeypatch
    ):
        create, result = self._invoke_tool(
            tmp_path, monkeypatch, instructions="Whisper conspiratorially."
        )
        assert result.get("success") is True
        assert create.call_args[1]["instructions"] == "Whisper conspiratorially."

    def test_tool_omits_instructions_when_not_supplied(
        self, tmp_path, monkeypatch
    ):
        create, result = self._invoke_tool(tmp_path, monkeypatch)
        assert result.get("success") is True
        assert "instructions" not in create.call_args[1]


# ---------------------------------------------------------------------------
# Schema
# ---------------------------------------------------------------------------

class TestSchema:
    def test_schema_exposes_instructions_parameter(self):
        from tools.tts_tool import TTS_SCHEMA
        props = TTS_SCHEMA["parameters"]["properties"]
        assert "instructions" in props
        assert props["instructions"]["type"] == "string"
        # Must stay optional — current behavior must be preserved.
        assert "instructions" not in TTS_SCHEMA["parameters"].get("required", [])
