# encoding:utf-8
"""
Unit tests for MiniMax provider additions:
  - MiniMax-M3 / M2.7 / M2.7-highspeed constants in const.py
  - Default model update in MinimaxBot
  - MinimaxVoice TTS provider
"""
import sys
import os
import json
import unittest
from unittest.mock import MagicMock, patch, PropertyMock

# Add project root to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))


class TestMinimaxConst(unittest.TestCase):
    """Test that MiniMax M3 / M2.7 constants are properly registered in const.py."""

    def test_m3_constant_defined(self):
        from common import const
        self.assertTrue(hasattr(const, "MINIMAX_M3"))
        self.assertEqual(const.MINIMAX_M3, "MiniMax-M3")

    def test_m2_7_highspeed_constant_defined(self):
        from common import const
        self.assertTrue(hasattr(const, "MINIMAX_M2_7_HIGHSPEED"))
        self.assertEqual(const.MINIMAX_M2_7_HIGHSPEED, "MiniMax-M2.7-highspeed")

    def test_m2_7_constant_defined(self):
        from common import const
        self.assertEqual(const.MINIMAX_M2_7, "MiniMax-M2.7")

    def test_m3_in_model_list(self):
        from common import const
        self.assertIn("MiniMax-M3", const.MODEL_LIST)

    def test_m2_7_highspeed_in_model_list(self):
        from common import const
        self.assertIn("MiniMax-M2.7-highspeed", const.MODEL_LIST)

    def test_m2_7_in_model_list(self):
        from common import const
        self.assertIn("MiniMax-M2.7", const.MODEL_LIST)

    def test_minimax_provider_key_defined(self):
        from common import const
        self.assertEqual(const.MiniMax, "minimax")


class TestMinimaxBotDefaultModel(unittest.TestCase):
    """Test that MinimaxBot defaults to MiniMax-M3."""

    def test_default_model_is_m3(self):
        # Patch conf() to return empty config
        mock_conf = MagicMock()
        mock_conf.get = MagicMock(side_effect=lambda key, default=None: default)

        with patch("models.minimax.minimax_bot.conf", return_value=mock_conf):
            with patch("models.minimax.minimax_bot.SessionManager"):
                from models.minimax import minimax_bot
                # Reload to pick up patches
                import importlib
                importlib.reload(minimax_bot)
                with patch("models.minimax.minimax_bot.conf", return_value=mock_conf):
                    bot = minimax_bot.MinimaxBot.__new__(minimax_bot.MinimaxBot)
                    bot.args = {
                        "model": mock_conf.get("model") or "MiniMax-M3",
                    }
                    self.assertEqual(bot.args["model"], "MiniMax-M3")

    def test_default_model_string(self):
        """Verify the fallback string literal in minimax_bot.py is MiniMax-M3."""
        import ast
        bot_path = os.path.join(os.path.dirname(__file__), "..", "models", "minimax", "minimax_bot.py")
        with open(bot_path) as f:
            source = f.read()
        # Verify MiniMax-M3 is in the source (not the older default)
        self.assertIn("MiniMax-M3", source)
        self.assertNotIn('"MiniMax-M2.1"', source)


class TestMinimaxVoice(unittest.TestCase):
    """Test MinimaxVoice TTS provider."""

    def _make_voice(self, api_key="test-key", api_base="https://api.minimax.io/v1"):
        mock_conf = MagicMock()
        def conf_get(key, default=None):
            return {
                "minimax_api_key": api_key,
                "minimax_api_base": api_base,
            }.get(key, default)
        mock_conf.get = conf_get
        with patch("voice.minimax.minimax_voice.conf", return_value=mock_conf):
            from voice.minimax.minimax_voice import MinimaxVoice
            return MinimaxVoice()

    def test_instantiation(self):
        voice = self._make_voice()
        self.assertIsNotNone(voice)

    def test_api_base_strips_v1_suffix(self):
        voice = self._make_voice(api_base="https://api.minimax.io/v1")
        self.assertEqual(voice.api_base, "https://api.minimax.io")

    def test_api_base_no_trailing_slash(self):
        voice = self._make_voice(api_base="https://api.minimax.io")
        self.assertEqual(voice.api_base, "https://api.minimax.io")

    def test_voice_to_text_not_supported(self):
        voice = self._make_voice()
        with self.assertRaises(NotImplementedError):
            voice.voiceToText("dummy.wav")

    def test_text_to_voice_success(self):
        """Test textToVoice with mocked SSE stream response."""
        import os
        os.makedirs("tmp", exist_ok=True)

        # Build fake SSE stream bytes
        audio_hex = bytes([0x49, 0x44, 0x33]).hex()  # "ID3" MP3 magic bytes
        sse_line = f'data: {{"data": {{"audio": "{audio_hex}", "status": 2}}}}\n\n'
        done_line = "data: [DONE]\n\n"
        fake_body = (sse_line + done_line).encode("utf-8")

        mock_response = MagicMock()
        mock_response.raise_for_status = MagicMock()
        mock_response.iter_lines.return_value = [
            line.encode("utf-8") for line in (sse_line + done_line).splitlines() if line
        ]

        mock_conf = MagicMock()
        def conf_get(key, default=None):
            return {
                "minimax_api_key": "test-key",
                "minimax_api_base": "https://api.minimax.io",
            }.get(key, default)
        mock_conf.get = conf_get

        with patch("voice.minimax.minimax_voice.conf", return_value=mock_conf):
            with patch("voice.minimax.minimax_voice.requests.post", return_value=mock_response):
                from voice.minimax import minimax_voice
                import importlib
                importlib.reload(minimax_voice)
                with patch("voice.minimax.minimax_voice.conf", return_value=mock_conf):
                    voice = minimax_voice.MinimaxVoice()
                    from bridge.reply import ReplyType
                    reply = voice.textToVoice("Hello, world!")
                    self.assertEqual(reply.type, ReplyType.VOICE)
                    self.assertTrue(reply.content.endswith(".mp3"))

    def test_text_to_voice_no_audio_returns_error(self):
        """Test that empty SSE stream returns an ERROR reply."""
        mock_response = MagicMock()
        mock_response.raise_for_status = MagicMock()
        mock_response.iter_lines.return_value = []

        mock_conf = MagicMock()
        def conf_get(key, default=None):
            return {
                "minimax_api_key": "test-key",
                "minimax_api_base": "https://api.minimax.io",
            }.get(key, default)
        mock_conf.get = conf_get

        with patch("voice.minimax.minimax_voice.conf", return_value=mock_conf):
            with patch("voice.minimax.minimax_voice.requests.post", return_value=mock_response):
                from voice.minimax import minimax_voice
                import importlib
                importlib.reload(minimax_voice)
                with patch("voice.minimax.minimax_voice.conf", return_value=mock_conf):
                    voice = minimax_voice.MinimaxVoice()
                    from bridge.reply import ReplyType
                    reply = voice.textToVoice("Hello")
                    self.assertEqual(reply.type, ReplyType.ERROR)


class TestVoiceFactory(unittest.TestCase):
    """Test that minimax is registered in the voice factory."""

    def test_minimax_voice_factory(self):
        mock_conf = MagicMock()
        mock_conf.get = MagicMock(return_value=None)
        with patch("voice.minimax.minimax_voice.conf", return_value=mock_conf):
            from voice.factory import create_voice
            voice = create_voice("minimax")
            from voice.minimax.minimax_voice import MinimaxVoice
            self.assertIsInstance(voice, MinimaxVoice)


if __name__ == "__main__":
    unittest.main()
