"""
Tests for MLC-LLM Provider

Unit tests using mocked HTTP responses to verify provider logic.
"""

import json
import unittest
from io import BytesIO
from unittest.mock import MagicMock, patch

from mcard.ptr.core.llm.providers.mlc_llm import MLCLLMProvider


class TestMLCLLMProvider(unittest.TestCase):
    """Test MLCLLMProvider implementation."""

    def test_provider_name(self):
        """Provider should have correct name."""
        provider = MLCLLMProvider()
        self.assertEqual(provider.provider_name, "mlc-llm")

    def test_default_base_url(self):
        """Provider should use default base URL from config."""
        provider = MLCLLMProvider()
        self.assertEqual(provider.base_url, "http://localhost:8000")

    def test_custom_base_url(self):
        """Provider should accept custom base URL."""
        provider = MLCLLMProvider(base_url="http://custom:9000/")
        self.assertEqual(
            provider.base_url, "http://custom:9000"
        )  # trailing slash stripped

    @patch("urllib.request.urlopen")
    def test_validate_connection_success(self, mock_urlopen):
        """validate_connection should return True on successful response."""
        mock_response = MagicMock()
        mock_response.read.return_value = json.dumps(
            {"data": [{"id": "model-1"}]}
        ).encode("utf-8")
        mock_response.__enter__ = MagicMock(return_value=mock_response)
        mock_response.__exit__ = MagicMock(return_value=False)
        mock_urlopen.return_value = mock_response

        provider = MLCLLMProvider()
        result = provider.validate_connection()

        self.assertTrue(result)
        mock_urlopen.assert_called_once()

    @patch("urllib.request.urlopen")
    def test_validate_connection_failure(self, mock_urlopen):
        """validate_connection should return False on connection error."""
        import urllib.error

        mock_urlopen.side_effect = urllib.error.URLError("Connection refused")

        provider = MLCLLMProvider()
        result = provider.validate_connection()

        self.assertFalse(result)

    @patch("urllib.request.urlopen")
    def test_list_models_success(self, mock_urlopen):
        """list_models should return model IDs from OpenAI format response."""
        mock_response = MagicMock()
        mock_response.read.return_value = json.dumps(
            {
                "data": [
                    {"id": "Llama-3-8B-Instruct-q4f16_1-MLC"},
                    {"id": "Phi-3-Mini-4k-Instruct-q4f16_1-MLC"},
                ]
            }
        ).encode("utf-8")
        mock_response.__enter__ = MagicMock(return_value=mock_response)
        mock_response.__exit__ = MagicMock(return_value=False)
        mock_urlopen.return_value = mock_response

        provider = MLCLLMProvider()
        result = provider.list_models()

        self.assertTrue(result.is_right())
        self.assertEqual(
            result.value,
            ["Llama-3-8B-Instruct-q4f16_1-MLC", "Phi-3-Mini-4k-Instruct-q4f16_1-MLC"],
        )

    @patch("urllib.request.urlopen")
    def test_complete_success(self, mock_urlopen):
        """complete should return text from OpenAI completions format."""
        mock_response = MagicMock()
        mock_response.read.return_value = json.dumps(
            {"choices": [{"text": "Hello, I am MLC!"}]}
        ).encode("utf-8")
        mock_response.__enter__ = MagicMock(return_value=mock_response)
        mock_response.__exit__ = MagicMock(return_value=False)
        mock_urlopen.return_value = mock_response

        provider = MLCLLMProvider()
        result = provider.complete("Say hello", {"temperature": 0.5})

        self.assertTrue(result.is_right())
        self.assertEqual(result.value, "Hello, I am MLC!")

    @patch("urllib.request.urlopen")
    def test_chat_success(self, mock_urlopen):
        """chat should return message from OpenAI chat format."""
        mock_response = MagicMock()
        mock_response.read.return_value = json.dumps(
            {
                "choices": [
                    {
                        "message": {
                            "role": "assistant",
                            "content": "I can help with that!",
                        }
                    }
                ],
                "model": "test-model",
                "usage": {"total_tokens": 50},
            }
        ).encode("utf-8")
        mock_response.__enter__ = MagicMock(return_value=mock_response)
        mock_response.__exit__ = MagicMock(return_value=False)
        mock_urlopen.return_value = mock_response

        provider = MLCLLMProvider()
        messages = [{"role": "user", "content": "Help me"}]
        result = provider.chat(messages, {"model": "test-model"})

        self.assertTrue(result.is_right())
        self.assertEqual(result.value["content"], "I can help with that!")
        self.assertEqual(result.value["role"], "assistant")
        self.assertEqual(result.value["model"], "test-model")

    @patch("urllib.request.urlopen")
    def test_complete_http_error(self, mock_urlopen):
        """complete should return Left on HTTP error."""
        import urllib.error

        error = urllib.error.HTTPError(
            url="http://localhost:8000/v1/completions",
            code=500,
            msg="Server Error",
            hdrs={},
            fp=BytesIO(b"Internal Server Error"),
        )
        mock_urlopen.side_effect = error

        provider = MLCLLMProvider()
        result = provider.complete("Test", {})

        self.assertTrue(result.is_left())
        self.assertIn("HTTP error 500", result.value)

    @patch("urllib.request.urlopen")
    def test_complete_unexpected_format(self, mock_urlopen):
        """complete should return Left on unexpected response format."""
        mock_response = MagicMock()
        mock_response.read.return_value = json.dumps({"unexpected": "format"}).encode(
            "utf-8"
        )
        mock_response.__enter__ = MagicMock(return_value=mock_response)
        mock_response.__exit__ = MagicMock(return_value=False)
        mock_urlopen.return_value = mock_response

        provider = MLCLLMProvider()
        result = provider.complete("Test", {})

        self.assertTrue(result.is_left())
        self.assertIn("Unexpected", result.value)


class TestMLCLLMProviderIntegration(unittest.TestCase):
    """Integration tests with LLMRuntime."""

    def test_get_provider_mlc_llm(self):
        """get_provider should return MLCLLMProvider for 'mlc-llm'."""
        from mcard.ptr.core.llm.runtime import get_provider

        provider = get_provider("mlc-llm")
        self.assertIsInstance(provider, MLCLLMProvider)
        self.assertEqual(provider.provider_name, "mlc-llm")

    def test_llm_runtime_with_mlc_llm(self):
        """LLMRuntime should instantiate with mlc-llm provider."""
        from mcard.ptr.core.llm.runtime import LLMRuntime

        runtime = LLMRuntime("mlc-llm")
        self.assertEqual(runtime.provider_name, "mlc-llm")
        self.assertEqual(runtime.provider.provider_name, "mlc-llm")


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