"""Tests for the model service module.

This module contains comprehensive tests for the ModelService and DummyModelService
classes, covering initialization, text processing, intent detection, slot filling,
verification, and utility methods.
"""

import json
from typing import Any
from unittest.mock import AsyncMock, MagicMock, Mock, patch

import pytest
from langchain_core.messages import HumanMessage, SystemMessage

from arklex.orchestrator.NLU.core.base import (
    IntentResponse,
)
from arklex.orchestrator.NLU.services.model_service import (
    DummyModelService,
    ModelService,
)
from arklex.utils.exceptions import ArklexError, ModelError, ValidationError


@pytest.fixture
def model_config() -> dict[str, Any]:
    """Fixture for model configuration."""
    return {
        "model_name": "gpt-4",
        "model_type_or_path": "gpt-4",
        "llm_provider": "openai",
        "api_key": "test_api_key",
        "endpoint": "https://api.openai.com/v1",
        "temperature": 0.1,
    }


@pytest.fixture
def dummy_config() -> dict[str, Any]:
    """Fixture for dummy model configuration."""
    return {
        "model_name": "test_model",
        "model_type_or_path": "test_path",
        "llm_provider": "openai",
        "api_key": "your_default_api_key",
        "endpoint": "https://api.openai.com/v1",
    }


@pytest.fixture
def model_service(model_config: dict[str, Any]) -> ModelService:
    """Fixture for ModelService instance."""
    with patch(
        "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
    ) as mock_get_model:
        mock_model = MagicMock()
        mock_get_model.return_value = mock_model
        service = ModelService(model_config)
        return service


@pytest.fixture
def dummy_model_service(dummy_config: dict[str, Any]) -> DummyModelService:
    """Fixture for DummyModelService instance."""
    return DummyModelService(dummy_config)


@pytest.fixture
def sample_intents() -> dict[str, Any]:
    """Fixture for sample intent definitions."""
    return {
        "greeting": {
            "definition": "User greets the assistant",
            "sample_utterances": ["hello", "hi", "good morning"],
        },
        "booking": {
            "definition": "User wants to book something",
            "sample_utterances": ["book a table", "make reservation"],
        },
    }


@pytest.fixture
def sample_slots() -> dict[str, Any]:
    """Fixture for sample slot definitions."""
    return {
        "date": {"type": "date", "description": "Date for booking", "required": True},
        "time": {"type": "time", "description": "Time for booking", "required": True},
    }


@pytest.fixture
def sample_verification_slots() -> dict[str, Any]:
    """Fixture for sample verification slot definitions."""
    return {
        "date": {"type": "date", "description": "Date for booking", "required": True},
        "time": {"type": "time", "description": "Time for booking", "required": True},
    }


class TestModelServiceInitialization:
    """Test cases for ModelService initialization and configuration validation."""

    def test_model_service_initialization_success(
        self, model_config: dict[str, Any]
    ) -> None:
        """Test successful model service initialization with valid configuration."""
        with patch(
            "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
        ) as mock_get_model:
            mock_model = MagicMock()
            mock_get_model.return_value = mock_model
            service = ModelService(model_config)
            assert service.model_config["model_name"] == "gpt-4"
            assert service.model_config["api_key"] == "test_api_key"
            assert service.model_config["endpoint"] == "https://api.openai.com/v1"

    def test_model_service_initialization_missing_config(self) -> None:
        """Test model service initialization with empty configuration raises error."""
        with pytest.raises(ValidationError) as exc_info:
            ModelService({})
        assert "Missing required field" in str(exc_info.value)

    def test_model_service_initialization_dummy_config(
        self, dummy_config: dict[str, Any]
    ) -> None:
        """Test model service initialization with dummy configuration."""
        with patch(
            "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
        ) as mock_get_model:
            mock_model = MagicMock()
            mock_get_model.return_value = mock_model
            service = ModelService(dummy_config)
            assert service.model_config["model_name"] == "test_model"
            assert service.model_config["llm_provider"] == "openai"

    def test_model_service_missing_model_name(self) -> None:
        """Test model service initialization with missing model name raises error."""
        incomplete_config = {"api_key": "key", "endpoint": "url"}
        with pytest.raises(ValidationError, match="Missing required field"):
            ModelService(incomplete_config)

    def test_model_service_missing_api_key(self) -> None:
        """Test model service initialization with missing API key raises error."""
        incomplete_config = {"model_name": "name", "endpoint": "url"}
        with pytest.raises(ValidationError, match="Missing required field"):
            ModelService(incomplete_config)

    def test_model_service_missing_endpoint(self) -> None:
        """Test model service initialization with missing endpoint raises error."""
        incomplete_config = {"model_name": "name", "api_key": "key"}
        with pytest.raises(ValidationError, match="Missing required field"):
            ModelService(incomplete_config)

    def test_validate_config_missing_fields(self, model_config: dict[str, Any]) -> None:
        """Test validate_config with missing required fields."""
        # Remove required fields
        invalid_config = model_config.copy()
        del invalid_config["model_name"]
        del invalid_config["api_key"]
        del invalid_config["endpoint"]

        with pytest.raises(ValidationError):
            ModelService(invalid_config)

    def test_validate_config_missing_model_name(
        self, model_config: dict[str, Any]
    ) -> None:
        """Test validate_config with missing model_name."""
        # Remove model_name
        invalid_config = model_config.copy()
        del invalid_config["model_name"]

        with pytest.raises(ValidationError):
            ModelService(invalid_config)

    def test_validate_config_with_defaults(self, model_config: dict[str, Any]) -> None:
        """Test validate_config with default values."""
        # Remove optional fields to test defaults
        config_with_defaults = {
            "model_name": model_config["model_name"],
            "api_key": model_config["api_key"],
            "endpoint": model_config["endpoint"],
            "model_type_or_path": model_config["model_type_or_path"],
            "llm_provider": model_config["llm_provider"],
        }

        with patch(
            "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
        ) as mock_get_model:
            mock_model = MagicMock()
            mock_get_model.return_value = mock_model
            service = ModelService(config_with_defaults)
            # Check that the service was initialized successfully
            assert service.model_config["model_name"] == model_config["model_name"]


class TestModelServiceTextProcessing:
    """Test cases for ModelService text processing methods."""

    @pytest.fixture
    def model_service(self) -> ModelService:
        """Fixture for ModelService instance."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "api_key": "test-key",
            "endpoint": "http://test.com",
        }
        with (
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
            ) as mock_get_model,
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.configure_response_format"
            ) as mock_configure,
        ):
            mock_model = MagicMock()
            mock_model.agenerate = AsyncMock()
            mock_get_model.return_value = mock_model
            mock_configure.return_value = mock_model
            service = ModelService(config)
            return service

    @pytest.mark.asyncio
    async def test_make_model_request(self, model_service: ModelService) -> None:
        """Test model request processing."""
        test_text = "Test input text"
        test_context = {"context": "test"}

        expected_response = MagicMock()
        expected_response.generations = [[MagicMock(text="success")]]

        with patch.object(
            model_service.model, "agenerate", new_callable=AsyncMock
        ) as mock_agenerate:
            mock_agenerate.return_value = expected_response

            actual_response = await model_service._make_model_request(
                {
                    "text": test_text,
                    "context": test_context,
                    "model": model_service.model_config["model_name"],
                }
            )

            assert actual_response == {"result": "success"}
            mock_agenerate.assert_called_once()

    @pytest.mark.asyncio
    async def test_process_text_request_failure(
        self, model_service: ModelService
    ) -> None:
        """Test text processing when model request fails."""
        test_text = "Test input text"

        with patch.object(
            model_service, "_make_model_request", new_callable=AsyncMock
        ) as mock_request:
            mock_request.side_effect = ModelError("Model request failed")

            with pytest.raises(ModelError, match="Model request failed"):
                await model_service.process_text(test_text)

    @pytest.mark.asyncio
    async def test_process_text_success(self, model_service: ModelService) -> None:
        """Test successful text processing."""
        test_text = "Test input text"
        expected_response = MagicMock()
        expected_response.generations = [[MagicMock(text="success")]]

        with patch.object(
            model_service.model, "agenerate", new_callable=AsyncMock
        ) as mock_agenerate:
            mock_agenerate.return_value = expected_response

            result = await model_service.process_text(test_text)
            assert result == {"result": "success"}

    @pytest.mark.asyncio
    async def test_process_text_with_whitespace_only(
        self, model_service: ModelService
    ) -> None:
        """Test text processing with whitespace-only input raises validation error."""
        with pytest.raises(ValidationError, match="Text cannot be empty"):
            await model_service.process_text("   ")


class TestModelServiceResponseHandling:
    """Test cases for ModelService response handling methods."""

    def test_model_service_get_response_success(
        self, dummy_model_service: DummyModelService
    ) -> None:
        """Test successful response generation."""
        prompt = "Test prompt"
        response = dummy_model_service.get_response(prompt)
        assert isinstance(response, str)
        assert len(response) > 0

    def test_model_service_get_response_empty(
        self, dummy_model_service: DummyModelService
    ) -> None:
        """Test response generation with empty prompt."""
        # The actual implementation doesn't validate empty prompts
        response = dummy_model_service.get_response("")
        assert isinstance(response, str)

    def test_model_service_get_response_model_error(
        self, dummy_model_service: DummyModelService
    ) -> None:
        """Test response generation when model raises error."""
        with (
            patch.object(
                dummy_model_service,
                "get_response",
                side_effect=ValueError("Model error"),
            ),
            pytest.raises(ValueError),
        ):
            dummy_model_service.get_response("test prompt")

    def test_get_json_response(self, model_service: ModelService) -> None:
        """Test JSON response generation."""
        prompt = "Return JSON: {'key': 'value'}"
        mock_response = MagicMock()
        mock_response.content = '{"key": "value"}'
        with patch.object(model_service.model, "invoke", return_value=mock_response):
            result = model_service.get_json_response(prompt)
            assert result == {"key": "value"}

    def test_get_response_with_model_config(self, model_service: ModelService) -> None:
        """Test response generation with model configuration."""
        prompt = "Test prompt"
        mock_response = MagicMock()
        mock_response.content = "Test response"
        with patch.object(model_service.model, "invoke", return_value=mock_response):
            result = model_service.get_response(prompt)
            assert result == "Test response"

    def test_get_response_with_system_prompt(self, model_service: ModelService) -> None:
        """Test response generation with system prompt."""
        prompt = "Test prompt"
        mock_response = MagicMock()
        mock_response.content = "Test response"
        with patch.object(model_service.model, "invoke", return_value=mock_response):
            result = model_service.get_response(
                prompt, system_prompt="You are a helpful assistant"
            )
            assert result == "Test response"

    def test_get_response_with_response_format(
        self, model_service: ModelService
    ) -> None:
        """Test response generation with response format."""
        prompt = "Test prompt"
        mock_response = MagicMock()
        mock_response.content = "Test response"
        with patch.object(model_service.model, "invoke", return_value=mock_response):
            result = model_service.get_response(
                prompt, response_format={"type": "text"}
            )
            assert result == "Test response"

    def test_get_response_with_note(self, model_service: ModelService) -> None:
        """Test response generation with note."""
        prompt = "Test prompt"
        mock_response = MagicMock()
        mock_response.content = "Test response"
        with patch.object(model_service.model, "invoke", return_value=mock_response):
            result = model_service.get_response(prompt, note="Important note")
            assert result == "Test response"

    def test_get_response_with_exception(self, model_service: ModelService) -> None:
        """Test response generation when model raises exception."""
        prompt = "Test prompt"
        with (
            patch.object(
                model_service.model, "invoke", side_effect=Exception("Model error")
            ),
            pytest.raises(ValueError),
        ):
            model_service.get_response(prompt)

    def test_get_json_response_with_exception(
        self, model_service: ModelService
    ) -> None:
        """Test JSON response generation when model raises exception."""
        prompt = "Test prompt"
        with (
            patch.object(
                model_service.model, "invoke", side_effect=Exception("Model error")
            ),
            pytest.raises(ValueError),
        ):
            model_service.get_json_response(prompt)


class TestModelServiceIntentProcessing:
    """Test cases for ModelService intent processing methods."""

    def test_format_intent_definition(self, model_service: ModelService) -> None:
        """Test formatting intent definitions."""
        result = model_service._format_intent_definition(
            "greeting", "User greets the assistant", 1
        )
        assert "greeting" in result
        assert "User greets the assistant" in result

    def test_format_intent_exemplars(self, model_service: ModelService) -> None:
        """Test formatting intent exemplars."""
        result = model_service._format_intent_exemplars("greeting", ["hello", "hi"], 1)
        assert "greeting" in result
        assert "hello" in result
        assert "hi" in result

    def test_process_intent(self, model_service: ModelService) -> None:
        """Test processing intent response."""
        response = {"intent": "greeting", "confidence": 0.95, "slots": {"name": "John"}}
        result = model_service._process_intent(
            "greeting", [response], 1, {"1": "greeting"}
        )
        assert isinstance(result, tuple)
        assert len(result) == 4

    def test_format_intent_input(self, model_service: ModelService) -> None:
        """Test formatting intent input."""
        text = "hello there"
        intents = {
            "greeting": [{"definition": "User greets", "sample_utterances": ["hello"]}]
        }
        result = model_service.format_intent_input(intents, text)
        assert isinstance(result, tuple)
        assert len(result) == 2


class TestModelServiceSlotProcessing:
    """Test cases for ModelService slot processing methods."""

    def test_format_slot_input(self, model_service: ModelService) -> None:
        """Test formatting slot input."""
        text = "book a table"
        slots = [{"name": "date", "type": "date", "description": "Date for booking"}]
        result = model_service.format_slot_input(slots, text)
        assert isinstance(result, tuple)
        assert len(result) == 2

    def test_process_slot_response(self, model_service: ModelService) -> None:
        """Test processing slot response."""
        response = {"slots": {"date": "tomorrow", "time": "7pm"}}
        slots = [{"name": "date", "type": "date"}]
        result = model_service.process_slot_response(json.dumps(response), slots)
        assert isinstance(result, list)

    def test_process_slot_response_with_invalid_json(
        self, model_service: ModelService
    ) -> None:
        """Test processing slot response with invalid JSON."""
        response = "invalid json"
        slots = [{"name": "date", "type": "date"}]
        with pytest.raises(ValueError):
            model_service.process_slot_response(response, slots)

    def test_process_slot_response_with_missing_slots(
        self, model_service: ModelService
    ) -> None:
        """Test processing slot response with missing slots."""
        response = {"slots": {"date": "tomorrow"}}
        slots = [{"name": "date", "type": "date"}, {"name": "time", "type": "time"}]
        result = model_service.process_slot_response(json.dumps(response), slots)
        assert isinstance(result, list)


class TestModelServiceVerification:
    """Test cases for ModelService slot verification methods."""

    def test_format_verification_input(self, model_service: ModelService) -> None:
        """Test formatting verification input."""
        slot = {"name": "date", "value": "tomorrow", "description": "desc"}
        chat_history = "User: book a table"
        result = model_service.format_verification_input(slot, chat_history)
        assert isinstance(result, str)

    def test_process_verification_response(self, model_service: ModelService) -> None:
        """Test processing verification response."""
        response = '{"verification_needed": true, "thought": "Need to verify"}'
        result = model_service.process_verification_response(response)
        assert isinstance(result, tuple)
        assert len(result) == 2


class TestModelServiceUtilities:
    """Test cases for ModelService utility methods."""

    def test_format_messages(self, model_service: ModelService) -> None:
        """Test message formatting."""
        prompt = "Test prompt"
        context = {"key": "value"}
        messages = model_service._format_messages(prompt, context)
        assert len(messages) == 2
        assert any("Context" in str(msg.content) for msg in messages)
        assert any("Test prompt" in str(msg.content) for msg in messages)

    def test_format_messages_with_context(self, model_service: ModelService) -> None:
        """Test message formatting with context."""
        prompt = "Test prompt"
        messages = model_service._format_messages(prompt)
        assert len(messages) == 1
        assert any("Test prompt" in str(msg.content) for msg in messages)


class TestDummyModelService:
    """Test cases for DummyModelService class."""

    def test_dummy_model_service_methods(
        self, dummy_model_service: DummyModelService
    ) -> None:
        """Test all DummyModelService methods return expected results."""
        # Test get_response
        response = dummy_model_service.get_response("test prompt")
        assert isinstance(response, str)
        assert len(response) > 0

        # Test get_json_response - now returns valid JSON
        json_response = dummy_model_service.get_json_response("test prompt")
        assert isinstance(json_response, dict)
        assert "result" in json_response

    def test_dummy_model_service_with_none_config(self) -> None:
        """Test DummyModelService initialization with None config."""
        with pytest.raises(ValidationError, match="Missing required field"):
            DummyModelService({})

    def test_dummy_model_service_get_response_with_none_prompt(
        self, dummy_model_service: DummyModelService
    ) -> None:
        """Test DummyModelService get_response with None prompt."""
        # The actual implementation doesn't validate None prompts
        response = dummy_model_service.get_response(None)
        assert isinstance(response, str)

    def test_dummy_model_service_get_response_with_empty_prompt(
        self, dummy_model_service: DummyModelService
    ) -> None:
        """Test DummyModelService get_response with empty prompt."""
        # The actual implementation doesn't validate empty prompts
        response = dummy_model_service.get_response("")
        assert isinstance(response, str)

    def test_dummy_model_service_get_json_response_with_none_prompt(
        self, dummy_model_service: DummyModelService
    ) -> None:
        """Test DummyModelService get_json_response with None prompt."""
        # Now returns valid JSON instead of raising error
        json_response = dummy_model_service.get_json_response(None)
        assert isinstance(json_response, dict)
        assert "result" in json_response

    def test_dummy_model_service_get_json_response_with_empty_prompt(
        self, dummy_model_service: DummyModelService
    ) -> None:
        """Test DummyModelService get_json_response with empty prompt."""
        # Now returns valid JSON instead of raising error
        json_response = dummy_model_service.get_json_response("")
        assert isinstance(json_response, dict)
        assert "result" in json_response


class TestModelServiceErrorHandling:
    """Test cases for ModelService error handling."""

    @pytest.mark.asyncio
    async def test_predict_intent_empty_response(
        self, model_service: ModelService
    ) -> None:
        """Test intent prediction with empty response."""
        text = "hello there"

        mock_response = MagicMock()
        mock_response.content = ""

        with patch.object(
            model_service.model, "invoke", new_callable=AsyncMock
        ) as mock_invoke:
            mock_invoke.return_value = mock_response

            with pytest.raises(ModelError, match="Empty response from model"):
                await model_service.predict_intent(text)

    @pytest.mark.asyncio
    async def test_predict_intent_json_decode_error(
        self, model_service: ModelService
    ) -> None:
        """Test intent prediction with JSON decode error."""
        text = "hello there"

        mock_response = MagicMock()
        mock_response.content = "invalid json"

        with patch.object(
            model_service.model, "invoke", new_callable=AsyncMock
        ) as mock_invoke:
            mock_invoke.return_value = mock_response

            with pytest.raises(ModelError, match="Failed to parse model response"):
                await model_service.predict_intent(text)

    @pytest.mark.asyncio
    async def test_predict_intent_validation_error(
        self, model_service: ModelService
    ) -> None:
        """Test intent prediction with validation error."""
        text = "hello there"

        with patch.object(
            model_service.model, "invoke", new_callable=AsyncMock
        ) as mock_invoke:
            mock_invoke.side_effect = ValidationError("Invalid response")

            with pytest.raises(ValidationError, match="Invalid response"):
                await model_service.predict_intent(text)

    @pytest.mark.asyncio
    async def test_fill_slots_empty_response(self, model_service: ModelService) -> None:
        """Test slot filling with empty response."""
        text = "book a table"

        mock_response = MagicMock()
        mock_response.content = ""

        with patch.object(
            model_service.model, "invoke", new_callable=AsyncMock
        ) as mock_invoke:
            mock_invoke.return_value = mock_response

            with pytest.raises(ModelError, match="Empty response from model"):
                await model_service.fill_slots(text, "booking")

    @pytest.mark.asyncio
    async def test_fill_slots_json_decode_error(
        self, model_service: ModelService
    ) -> None:
        """Test slot filling with JSON decode error."""
        text = "book a table"

        mock_response = MagicMock()
        mock_response.content = "invalid json"

        with patch.object(
            model_service.model, "invoke", new_callable=AsyncMock
        ) as mock_invoke:
            mock_invoke.return_value = mock_response

            with pytest.raises(ModelError, match="Failed to parse slot response"):
                await model_service.fill_slots(text, "booking")

    @pytest.mark.asyncio
    async def test_verify_slots_empty_response(
        self, model_service: ModelService
    ) -> None:
        """Test slot verification with empty response."""
        text = "confirm booking"
        slots = {"date": "tomorrow"}

        mock_response = MagicMock()
        mock_response.content = ""

        with patch.object(
            model_service.model, "invoke", new_callable=AsyncMock
        ) as mock_invoke:
            mock_invoke.return_value = mock_response

            with pytest.raises(ModelError, match="Empty response from model"):
                await model_service.verify_slots(text, slots)

    @pytest.mark.asyncio
    async def test_verify_slots_json_decode_error(
        self, model_service: ModelService
    ) -> None:
        """Test slot verification with JSON decode error."""
        text = "confirm booking"
        slots = {"date": "tomorrow"}

        mock_response = MagicMock()
        mock_response.content = "invalid json"

        with patch.object(
            model_service.model, "invoke", new_callable=AsyncMock
        ) as mock_invoke:
            mock_invoke.return_value = mock_response

            with pytest.raises(
                ModelError, match="Failed to parse verification response"
            ):
                await model_service.verify_slots(text, slots)

    @pytest.mark.asyncio
    async def test_verify_slots_validation_error(
        self, model_service: ModelService
    ) -> None:
        """Test slot verification with validation error."""
        text = "confirm booking"
        slots = {"date": "tomorrow"}

        with patch.object(
            model_service.model, "invoke", new_callable=AsyncMock
        ) as mock_invoke:
            mock_invoke.side_effect = ValidationError("Invalid response")

            with pytest.raises(ValidationError, match="Invalid response"):
                await model_service.verify_slots(text, slots)


class TestModelServiceInitializationErrors:
    """Test ModelService initialization error scenarios."""

    def test_model_service_initialization_with_exception(self) -> None:
        """Test ModelService initialization when an exception occurs."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "api_key": "test-key",
            "endpoint": "http://test.com",
        }

        with patch(
            "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance",
            side_effect=Exception("Initialization failed"),
        ):
            with pytest.raises(ModelError) as exc_info:
                ModelService(config)

            assert "Failed to initialize model service" in str(exc_info.value)

    def test_model_service_initialization_with_api_service_exception(self) -> None:
        """Test ModelService initialization when APIClientService fails."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "api_key": "test-key",
            "endpoint": "http://test.com",
        }

        with patch(
            "arklex.orchestrator.NLU.services.model_service.APIClientService",
            side_effect=Exception("API service failed"),
        ):
            with pytest.raises(ModelError) as exc_info:
                ModelService(config)

            assert "Failed to initialize model service" in str(exc_info.value)


class TestModelServiceConfigDefaults:
    """Test ModelService configuration default values."""

    def test_validate_config_with_default_api_key(self) -> None:
        """Test config validation when api_key is missing and defaults are used."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "endpoint": "http://test.com",
        }

        with (
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
            ) as mock_get_model,
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.configure_response_format"
            ) as mock_configure,
        ):
            mock_model = MagicMock()
            mock_get_model.return_value = mock_model
            mock_configure.return_value = None

            service = ModelService(config)

            # Check that default api_key was added
            assert "api_key" in service.model_config
            assert service.model_config["api_key"] is not None

    def test_validate_config_with_default_endpoint(self) -> None:
        """Test config validation when endpoint is missing and defaults are used."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "api_key": "test-key",
        }

        with (
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
            ) as mock_get_model,
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.configure_response_format"
            ) as mock_configure,
        ):
            mock_model = MagicMock()
            mock_get_model.return_value = mock_model
            mock_configure.return_value = None

            service = ModelService(config)

            # Check that default endpoint was added
            assert "endpoint" in service.model_config
            assert service.model_config["endpoint"] is not None


class TestModelServiceProcessTextExceptions:
    """Test ModelService process_text exception handling."""

    @pytest.fixture
    def model_service_with_mock_model(self) -> ModelService:
        """Fixture for ModelService with mocked model."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "api_key": "test-key",
            "endpoint": "http://test.com",
        }
        with (
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
            ) as mock_get_model,
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.configure_response_format"
            ) as mock_configure,
        ):
            mock_model = MagicMock()
            mock_model.agenerate = AsyncMock()
            mock_get_model.return_value = mock_model
            mock_configure.return_value = None
            service = ModelService(config)
            return service

    @pytest.mark.asyncio
    async def test_process_text_with_model_request_exception(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test process_text when _make_model_request raises an exception."""
        # Mock _make_model_request to raise an exception
        with patch.object(
            model_service_with_mock_model,
            "_make_model_request",
            side_effect=Exception("Model request failed"),
        ):
            with pytest.raises(ModelError) as exc_info:
                await model_service_with_mock_model.process_text("test text")

            assert "Model request failed" in str(exc_info.value)
            assert exc_info.value.details["error"] == "Model request failed"
            assert exc_info.value.details["text"] == "test text"

    @pytest.mark.asyncio
    async def test_process_text_with_non_string_input(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test process_text with non-string input."""
        with pytest.raises(Exception) as exc_info:
            await model_service_with_mock_model.process_text(123)  # type: ignore
        # Should raise ArklexError due to error handler
        assert "ArklexError" in type(exc_info.value).__name__


class TestModelServiceMakeModelRequestExceptions:
    """Test ModelService _make_model_request exception handling."""

    @pytest.fixture
    def model_service_with_mock_model(self) -> ModelService:
        """Fixture for ModelService with mocked model."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "api_key": "test-key",
            "endpoint": "http://test.com",
        }
        with (
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
            ) as mock_get_model,
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.configure_response_format"
            ) as mock_configure,
        ):
            mock_model = MagicMock()
            mock_model.agenerate = AsyncMock()
            mock_get_model.return_value = mock_model
            mock_configure.return_value = None
            service = ModelService(config)
            # Ensure the model is properly set
            service.model = mock_model
            return service

    @pytest.mark.asyncio
    async def test_make_model_request_with_agenerate_exception(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test _make_model_request when agenerate raises an exception."""
        # Mock agenerate to raise an exception
        model_service_with_mock_model.model.agenerate.side_effect = Exception(
            "Model generation failed"
        )

        with pytest.raises(ModelError) as exc_info:
            await model_service_with_mock_model._make_model_request("test text")

        assert "Model generation failed" in str(exc_info.value)
        assert exc_info.value.details["error"] == "Model generation failed"
        assert exc_info.value.details["text"] == "test text"

    @pytest.mark.asyncio
    async def test_make_model_request_with_dict_input(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test _make_model_request with dictionary input."""
        # Mock successful response
        mock_response = MagicMock()
        mock_generation = MagicMock()
        mock_generation.text = "test response"
        mock_response.generations = [[mock_generation]]
        model_service_with_mock_model.model.agenerate.return_value = mock_response

        # Mock _format_messages
        with patch.object(
            model_service_with_mock_model,
            "_format_messages",
            return_value=[MagicMock()],
        ):
            result = await model_service_with_mock_model._make_model_request(
                {
                    "text": "test text",
                    "context": {"key": "value"},
                    "model": "test-model",
                }
            )

        assert result == {"result": "test response"}


class TestModelServiceAsyncMethods:
    """Test ModelService async methods with comprehensive coverage."""

    @pytest.fixture
    def model_service_with_mock_model(self) -> ModelService:
        """Fixture for ModelService with mocked model."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "api_key": "test-key",
            "endpoint": "http://test.com",
        }
        with (
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
            ) as mock_get_model,
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.configure_response_format"
            ) as mock_configure,
        ):
            mock_model = MagicMock()
            mock_model.invoke = AsyncMock()
            mock_get_model.return_value = mock_model
            mock_configure.return_value = None
            service = ModelService(config)
            # Ensure the model is properly set
            service.model = mock_model
            return service

    @pytest.mark.asyncio
    async def test_predict_intent_success(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test successful intent prediction."""
        # Mock successful response
        mock_response = MagicMock()
        mock_response.content = '{"intent": "test_intent", "confidence": 0.95}'
        model_service_with_mock_model.model.invoke.return_value = mock_response

        with patch(
            "arklex.orchestrator.NLU.services.model_service.validate_intent_response",
            return_value={"intent": "test_intent", "confidence": 0.95},
        ):
            result = await model_service_with_mock_model.predict_intent("test text")

        assert isinstance(result, IntentResponse)
        assert result.intent == "test_intent"
        assert result.confidence == 0.95

    @pytest.mark.asyncio
    async def test_predict_intent_empty_response(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test intent prediction with empty response."""
        # Mock empty response
        mock_response = MagicMock()
        mock_response.content = None
        model_service_with_mock_model.model.invoke.return_value = mock_response

        with pytest.raises(ModelError) as exc_info:
            await model_service_with_mock_model.predict_intent("test text")

        assert "Empty response from model" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_predict_intent_json_decode_error(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test intent prediction with JSON decode error."""
        # Mock response with invalid JSON
        mock_response = MagicMock()
        mock_response.content = "invalid json"
        model_service_with_mock_model.model.invoke.return_value = mock_response

        with pytest.raises(ModelError) as exc_info:
            await model_service_with_mock_model.predict_intent("test text")

        assert "Failed to parse model response" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_predict_intent_validation_error(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test intent prediction with validation error."""
        # Mock response with valid JSON but invalid structure
        mock_response = MagicMock()
        mock_response.content = '{"invalid": "structure"}'
        model_service_with_mock_model.model.invoke.return_value = mock_response

        with patch(
            "arklex.orchestrator.NLU.services.model_service.validate_intent_response",
            side_effect=ValidationError("Invalid structure"),
        ):
            with pytest.raises(ValidationError) as exc_info:
                await model_service_with_mock_model.predict_intent("test text")

            assert "Invalid intent response format" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_fill_slots_success(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test successful slot filling."""
        # Mock successful response
        mock_response = MagicMock()
        mock_response.content = '{"slot": "test_slot", "value": "test_value", "confidence": 0.9, "slots": [{"name": "test_slot", "value": "test_value"}]}'
        model_service_with_mock_model.model.invoke.return_value = mock_response

        with patch(
            "arklex.orchestrator.NLU.services.model_service.validate_slot_response",
            return_value={
                "slot": "test_slot",
                "value": "test_value",
                "confidence": 0.9,
                "slots": [{"name": "test_slot", "value": "test_value"}],
            },
        ):
            result = await model_service_with_mock_model.fill_slots(
                "test text", "test_intent"
            )

        assert result.slot == "test_slot"
        assert result.value == "test_value"
        assert result.confidence == 0.9

    @pytest.mark.asyncio
    async def test_fill_slots_empty_response(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test slot filling with empty response."""
        # Mock empty response
        mock_response = MagicMock()
        mock_response.content = None
        model_service_with_mock_model.model.invoke.return_value = mock_response

        with pytest.raises(ModelError) as exc_info:
            await model_service_with_mock_model.fill_slots("test text", "test_intent")

        assert "Empty response from model" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_fill_slots_json_decode_error(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test slot filling with JSON decode error."""
        # Mock response with invalid JSON
        mock_response = MagicMock()
        mock_response.content = "invalid json"
        model_service_with_mock_model.model.invoke.return_value = mock_response

        with pytest.raises(ModelError) as exc_info:
            await model_service_with_mock_model.fill_slots("test text", "test_intent")

        assert "Failed to parse slot response" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_verify_slots_success(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test successful slot verification."""
        # Mock successful response
        mock_response = MagicMock()
        mock_response.content = '{"slot": "test_slot", "verified": true, "verification_needed": false, "reason": "Valid"}'
        model_service_with_mock_model.model.invoke.return_value = mock_response

        with patch(
            "arklex.orchestrator.NLU.services.model_service.validate_verification_response",
            return_value={
                "slot": "test_slot",
                "verified": True,
                "verification_needed": False,
                "reason": "Valid",
            },
        ):
            result = await model_service_with_mock_model.verify_slots(
                "test text", {"test_slot": "test_value"}
            )

        assert result.slot == "test_slot"
        assert result.verified is True
        assert result.reason == "Valid"

    @pytest.mark.asyncio
    async def test_verify_slots_empty_response(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test slot verification with empty response."""
        # Mock empty response
        mock_response = MagicMock()
        mock_response.content = None
        model_service_with_mock_model.model.invoke.return_value = mock_response

        with pytest.raises(ModelError) as exc_info:
            await model_service_with_mock_model.verify_slots(
                "test text", {"test_slot": "test_value"}
            )

        assert "Empty response from model" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_verify_slots_json_decode_error(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test slot verification with JSON decode error."""
        # Mock response with invalid JSON
        mock_response = MagicMock()
        mock_response.content = "invalid json"
        model_service_with_mock_model.model.invoke.return_value = mock_response

        with pytest.raises(ModelError) as exc_info:
            await model_service_with_mock_model.verify_slots(
                "test text", {"test_slot": "test_value"}
            )

        assert "Failed to parse verification response" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_predict_intent_invalid_input(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test intent prediction with invalid input."""
        with pytest.raises(ValidationError) as exc_info:
            await model_service_with_mock_model.predict_intent("")

        assert "Invalid input text" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_fill_slots_invalid_text(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test slot filling with invalid text."""
        with pytest.raises(ValidationError) as exc_info:
            await model_service_with_mock_model.fill_slots("", "test_intent")

        assert "Invalid input text" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_fill_slots_invalid_intent(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test slot filling with invalid intent."""
        with pytest.raises(ValidationError) as exc_info:
            await model_service_with_mock_model.fill_slots("test text", "")

        assert "Invalid intent" in str(exc_info.value)


class TestModelServiceProcessSlotResponse:
    """Test ModelService process_slot_response method."""

    @pytest.fixture
    def model_service(self) -> ModelService:
        """Fixture for ModelService instance."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "api_key": "test-key",
            "endpoint": "http://test.com",
        }
        with (
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
            ) as mock_get_model,
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.configure_response_format"
            ) as mock_configure,
        ):
            mock_model = MagicMock()
            mock_get_model.return_value = mock_model
            mock_configure.return_value = None
            service = ModelService(config)
            return service

    def test_process_slot_response_success(self, model_service: ModelService) -> None:
        """Test successful slot response processing."""
        response = '{"slot1": "value1", "slot2": "value2"}'
        slots = [
            {"name": "slot1", "type": "string"},
            {"name": "slot2", "type": "string"},
            {"name": "slot3", "type": "string"},
        ]

        result = model_service.process_slot_response(response, slots)

        assert len(result) == 3
        assert result[0]["value"] == "value1"
        assert result[1]["value"] == "value2"
        assert result[2]["value"] is None

    def test_process_slot_response_json_decode_error(
        self, model_service: ModelService
    ) -> None:
        """Test slot response processing with JSON decode error."""
        response = "invalid json"
        slots = [{"name": "slot1", "type": "string"}]

        with pytest.raises(ValueError) as exc_info:
            model_service.process_slot_response(response, slots)

        assert "Failed to parse slot filling response" in str(exc_info.value)

    def test_process_slot_response_general_exception(
        self, model_service: ModelService
    ) -> None:
        """Test slot response processing with general exception."""
        response = '{"slot1": "value1"}'
        slots = [{"name": "slot1", "type": "string"}]

        # Mock json.loads to raise a general exception
        with patch("json.loads", side_effect=Exception("General error")):
            with pytest.raises(ValueError) as exc_info:
                model_service.process_slot_response(response, slots)

            assert "Failed to process slot filling response" in str(exc_info.value)


class TestModelServiceProcessVerificationResponse:
    """Test ModelService process_verification_response method."""

    @pytest.fixture
    def model_service(self) -> ModelService:
        """Fixture for ModelService instance."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "api_key": "test-key",
            "endpoint": "http://test.com",
        }
        with (
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
            ) as mock_get_model,
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.configure_response_format"
            ) as mock_configure,
        ):
            mock_model = MagicMock()
            mock_get_model.return_value = mock_model
            mock_configure.return_value = None
            service = ModelService(config)
            return service

    def test_process_verification_response_success(
        self, model_service: ModelService
    ) -> None:
        """Test successful verification response processing."""
        response = '{"verification_needed": false, "thought": "Valid value"}'

        verification_needed, thought = model_service.process_verification_response(
            response
        )

        assert verification_needed is False
        assert thought == "Valid value"

    def test_process_verification_response_json_decode_error(
        self, model_service: ModelService
    ) -> None:
        """Test verification response processing with JSON decode error."""
        response = "invalid json"

        verification_needed, thought = model_service.process_verification_response(
            response
        )

        assert verification_needed is True
        assert "Failed to parse verification response" in thought


class TestDummyModelServiceExtended:
    """Test DummyModelService extended functionality."""

    @pytest.fixture
    def dummy_model_service(self) -> DummyModelService:
        """Fixture for DummyModelService instance."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "api_key": "test-key",
            "endpoint": "http://test.com",
        }
        with (
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
            ) as mock_get_model,
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.configure_response_format"
            ) as mock_configure,
        ):
            mock_model = MagicMock()
            mock_get_model.return_value = mock_model
            mock_configure.return_value = None
            service = DummyModelService(config)
            return service

    def test_dummy_model_service_get_response_returns_mock(
        self, dummy_model_service: DummyModelService
    ) -> None:
        """Test that DummyModelService.get_response returns the expected mock value."""
        result = dummy_model_service.get_response("test prompt")
        assert result == "1) others"

    def test_dummy_model_service_get_response_with_all_parameters(
        self, dummy_model_service: DummyModelService
    ) -> None:
        """Test DummyModelService.get_response with all parameters."""
        result = dummy_model_service.get_response(
            prompt="test prompt",
            model_config={"test": "config"},
            system_prompt="test system",
            response_format="json",
            note="test note",
        )
        assert result == "1) others"

    def test_dummy_model_service_process_slot_response_calls_super(
        self, dummy_model_service: DummyModelService
    ) -> None:
        """Test that DummyModelService.process_slot_response calls super method."""
        response = '{"slot1": "value1"}'
        slots = [{"name": "slot1", "type": "string"}]

        result = dummy_model_service.process_slot_response(response, slots)

        assert len(result) == 1
        assert result[0]["value"] == "value1"

    def test_dummy_model_service_format_verification_input_calls_super(
        self, dummy_model_service: DummyModelService
    ) -> None:
        """Test that DummyModelService.format_verification_input calls super method."""
        slot = {
            "name": "test_slot",
            "value": "test_value",
            "description": "Test slot description",
        }
        chat_history = "test history"

        result = dummy_model_service.format_verification_input(slot, chat_history)

        # Should return a string (from the formatter)
        assert isinstance(result, str)

    def test_dummy_model_service_process_verification_response_calls_super(
        self, dummy_model_service: DummyModelService
    ) -> None:
        """Test that DummyModelService.process_verification_response calls super method."""
        response = '{"verification_needed": true, "thought": "Needs verification"}'

        verification_needed, thought = (
            dummy_model_service.process_verification_response(response)
        )

        assert verification_needed is True
        assert thought == "Needs verification"


class TestModelServiceFormatMessages:
    """Test ModelService _format_messages method."""

    @pytest.fixture
    def model_service(self) -> ModelService:
        """Fixture for ModelService instance."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "api_key": "test-key",
            "endpoint": "http://test.com",
        }
        with (
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
            ) as mock_get_model,
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.configure_response_format"
            ) as mock_configure,
        ):
            mock_model = MagicMock()
            mock_get_model.return_value = mock_model
            mock_configure.return_value = None
            service = ModelService(config)
            return service

    def test_format_messages_with_context(self, model_service: ModelService) -> None:
        """Test _format_messages with context."""
        prompt = "test prompt"
        context = {"key": "value"}

        messages = model_service._format_messages(prompt, context)

        assert len(messages) == 2
        assert isinstance(messages[0], SystemMessage)
        assert isinstance(messages[1], HumanMessage)
        assert 'Context: {"key": "value"}' in messages[0].content
        assert prompt in messages[1].content

    def test_format_messages_without_context(self, model_service: ModelService) -> None:
        """Test _format_messages without context."""
        prompt = "test prompt"

        messages = model_service._format_messages(prompt)

        assert len(messages) == 1
        assert isinstance(messages[0], HumanMessage)
        assert prompt in messages[0].content


class TestModelServiceFormatIntentInput:
    """Test ModelService format_intent_input method."""

    @pytest.fixture
    def model_service(self) -> ModelService:
        """Fixture for ModelService instance."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "api_key": "test-key",
            "endpoint": "http://test.com",
        }
        with (
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
            ) as mock_get_model,
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.configure_response_format"
            ) as mock_configure,
        ):
            mock_model = MagicMock()
            mock_get_model.return_value = mock_model
            mock_configure.return_value = None
            service = ModelService(config)
            return service

    def test_format_intent_input_with_complex_intents(
        self, model_service: ModelService
    ) -> None:
        """Test format_intent_input with complex intent structure."""
        intents = {
            "intent1": [
                {"definition": "Test intent 1", "exemplars": ["example 1", "example 2"]}
            ],
            "intent2": [{"definition": "Test intent 2", "exemplars": ["example 3"]}],
        }
        chat_history = "test history"

        prompt, mapping = model_service.format_intent_input(intents, chat_history)

        assert isinstance(prompt, str)
        assert isinstance(mapping, dict)
        assert "intent1" in prompt
        assert "intent2" in prompt
        assert "test history" in prompt
        assert len(mapping) == 2

    def test_format_intent_input_with_empty_intents(
        self, model_service: ModelService
    ) -> None:
        """Test format_intent_input with empty intents."""
        intents = {}
        chat_history = "test history"

        prompt, mapping = model_service.format_intent_input(intents, chat_history)

        assert isinstance(prompt, str)
        assert isinstance(mapping, dict)
        assert len(mapping) == 0


class TestModelServiceFormatSlotInput:
    """Test ModelService format_slot_input method."""

    @pytest.fixture
    def model_service(self) -> ModelService:
        """Fixture for ModelService instance."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "api_key": "test-key",
            "endpoint": "http://test.com",
        }
        with (
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
            ) as mock_get_model,
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.configure_response_format"
            ) as mock_configure,
        ):
            mock_model = MagicMock()
            mock_get_model.return_value = mock_model
            mock_configure.return_value = None
            service = ModelService(config)
            return service

    def test_format_slot_input_with_enum_values(
        self, model_service: ModelService
    ) -> None:
        """Test format_slot_input with slots that have enum values."""
        slots = [
            {
                "name": "test_slot",
                "type": "string",
                "required": True,
                "description": "Test slot",
                "items": {"enum": ["value1", "value2", "value3"]},
            }
        ]
        context = "test context"

        user_prompt, system_prompt = model_service.format_slot_input(slots, context)

        assert isinstance(user_prompt, str)
        assert isinstance(system_prompt, str)
        assert "test_slot" in user_prompt
        assert "value1, value2, value3" in user_prompt
        assert "slot filling assistant" in system_prompt

    def test_format_slot_input_with_dict_items(
        self, model_service: ModelService
    ) -> None:
        """Test format_slot_input with dict items."""
        slots = [
            {
                "name": "test_slot",
                "type": "string",
                "required": True,
                "description": "Test slot",
                "items": {"enum": ["value1", "value2"]},
            }
        ]
        context = "test context"

        user_prompt, system_prompt = model_service.format_slot_input(slots, context)

        assert "value1, value2" in user_prompt

    def test_format_slot_input_with_object_items(
        self, model_service: ModelService
    ) -> None:
        """Test format_slot_input with object items."""
        # Create a mock object with enum attribute
        mock_items = MagicMock()
        mock_items.enum = ["value1", "value2"]

        slots = [
            {
                "name": "test_slot",
                "type": "string",
                "required": True,
                "description": "Test slot",
                "items": mock_items,
            }
        ]
        context = "test context"

        user_prompt, system_prompt = model_service.format_slot_input(slots, context)

        assert "value1, value2" in user_prompt

    def test_format_slot_input_without_enum_values(
        self, model_service: ModelService
    ) -> None:
        """Test format_slot_input with slots without enum values."""
        slots = [
            {
                "name": "test_slot",
                "type": "string",
                "description": "Test slot",
                "required": False,
                "items": {},
            }
        ]
        context = "test context"

        user_prompt, system_prompt = model_service.format_slot_input(slots, context)

        assert "test_slot" in user_prompt
        assert "optional" in user_prompt
        assert "enum" not in user_prompt.lower()


class TestModelServiceProcessSlotResponseWithPydantic:
    """Test ModelService process_slot_response with Pydantic models."""

    @pytest.fixture
    def model_service(self) -> ModelService:
        """Fixture for ModelService instance."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "api_key": "test-key",
            "endpoint": "http://test.com",
        }
        with (
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
            ) as mock_get_model,
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.configure_response_format"
            ) as mock_configure,
        ):
            mock_model = MagicMock()
            mock_get_model.return_value = mock_model
            mock_configure.return_value = None
            service = ModelService(config)
            return service

    def test_process_slot_response_with_pydantic_models(
        self, model_service: ModelService
    ) -> None:
        """Test process_slot_response with Pydantic model slots."""
        response = '{"slot1": "value1", "slot2": "value2"}'

        # Create mock Pydantic-like objects
        slot1 = MagicMock()
        slot1.name = "slot1"
        slot2 = MagicMock()
        slot2.name = "slot2"
        slot3 = MagicMock()
        slot3.name = "slot3"

        slots = [slot1, slot2, slot3]

        result = model_service.process_slot_response(response, slots)

        assert len(result) == 3
        assert result[0].value == "value1"
        assert result[1].value == "value2"
        assert result[2].value is None

    def test_process_slot_response_with_mixed_types(
        self, model_service: ModelService
    ) -> None:
        """Test process_slot_response with mixed dict and Pydantic model slots."""
        response = '{"slot1": "value1", "slot2": "value2"}'

        slot1 = {"name": "slot1", "type": "string"}
        slot2 = MagicMock()
        slot2.name = "slot2"

        slots = [slot1, slot2]

        result = model_service.process_slot_response(response, slots)

        assert len(result) == 2
        assert result[0]["value"] == "value1"
        assert result[1].value == "value2"


class TestModelServiceFormatVerificationInput:
    """Test ModelService format_verification_input method."""

    @pytest.fixture
    def model_service(self) -> ModelService:
        """Fixture for ModelService instance."""
        config = {
            "model_name": "test-model",
            "model_type_or_path": "test-path",
            "llm_provider": "openai",
            "api_key": "test-key",
            "endpoint": "http://test.com",
        }
        with (
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
            ) as mock_get_model,
            patch(
                "arklex.orchestrator.NLU.services.model_service.ModelConfig.configure_response_format"
            ) as mock_configure,
        ):
            mock_model = MagicMock()
            mock_get_model.return_value = mock_model
            mock_configure.return_value = None
            service = ModelService(config)
            return service

    def test_format_verification_input_calls_formatter(
        self, model_service: ModelService
    ) -> None:
        """Test that format_verification_input calls the formatter function."""
        slot = {"name": "test_slot", "value": "test_value"}
        chat_history = "test history"

        with patch(
            "arklex.orchestrator.NLU.services.model_service.format_verification_input_formatter",
            return_value="formatted verification input",
        ) as mock_formatter:
            result = model_service.format_verification_input(slot, chat_history)

            mock_formatter.assert_called_once_with(slot, chat_history)
            assert result == "formatted verification input"


class TestModelServiceMissingCoverage:
    """Test cases to cover missing lines in ModelService."""

    @pytest.fixture
    def model_service_with_mock_model(
        self, model_config: dict[str, Any]
    ) -> ModelService:
        """Fixture for ModelService with mocked model."""
        with patch(
            "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
        ) as mock_get_model:
            mock_model = MagicMock()
            mock_get_model.return_value = mock_model
            service = ModelService(model_config)
            return service

    @pytest.mark.asyncio
    async def test_process_text_with_model_request_exception(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test process_text when _make_model_request raises an exception."""
        # Mock _make_model_request to raise an exception
        with patch.object(
            model_service_with_mock_model, "_make_model_request"
        ) as mock_request:
            mock_request.side_effect = Exception("Model request failed")

            with pytest.raises(ModelError) as exc_info:
                await model_service_with_mock_model.process_text("test text")

            assert "Model request failed" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_fill_slots_with_empty_response_exception(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test fill_slots when model returns empty response."""
        # Mock model.invoke to return empty response
        mock_response = MagicMock()
        mock_response.content = None
        model_service_with_mock_model.model.invoke = AsyncMock(
            return_value=mock_response
        )

        with pytest.raises(ModelError) as exc_info:
            await model_service_with_mock_model.fill_slots("test text", "test_intent")

        assert "Empty response from model" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_fill_slots_with_json_decode_exception(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test fill_slots when JSON parsing fails."""
        # Mock model.invoke to return invalid JSON
        mock_response = MagicMock()
        mock_response.content = "invalid json"
        model_service_with_mock_model.model.invoke = AsyncMock(
            return_value=mock_response
        )

        with pytest.raises(ModelError) as exc_info:
            await model_service_with_mock_model.fill_slots("test text", "test_intent")

        assert "Failed to parse slot response" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_fill_slots_with_validation_exception(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test fill_slots when validation fails."""
        # Mock model.invoke to return invalid response format
        mock_response = MagicMock()
        mock_response.content = '{"invalid": "format"}'
        model_service_with_mock_model.model.invoke = AsyncMock(
            return_value=mock_response
        )

        # Mock validate_slot_response to raise ValidationError
        with patch(
            "arklex.orchestrator.NLU.services.model_service.validate_slot_response"
        ) as mock_validate:
            mock_validate.side_effect = ValidationError("Invalid format")

            with pytest.raises(ValidationError) as exc_info:
                await model_service_with_mock_model.fill_slots(
                    "test text", "test_intent"
                )

            assert "Invalid slot response format" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_verify_slots_with_empty_response_exception(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test verify_slots when model returns empty response."""
        # Mock model.invoke to return empty response
        mock_response = MagicMock()
        mock_response.content = None
        model_service_with_mock_model.model.invoke = AsyncMock(
            return_value=mock_response
        )

        with pytest.raises(ModelError) as exc_info:
            await model_service_with_mock_model.verify_slots(
                "test text", {"test_slot": "test_value"}
            )

        assert "Empty response from model" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_verify_slots_with_json_decode_exception(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test verify_slots when JSON parsing fails."""
        # Mock model.invoke to return invalid JSON
        mock_response = MagicMock()
        mock_response.content = "invalid json"
        model_service_with_mock_model.model.invoke = AsyncMock(
            return_value=mock_response
        )

        with pytest.raises(ModelError) as exc_info:
            await model_service_with_mock_model.verify_slots(
                "test text", {"test_slot": "test_value"}
            )

        assert "Failed to parse verification response" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_verify_slots_with_validation_exception(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test verify_slots when validation fails."""
        # Mock model.invoke to return invalid response format
        mock_response = MagicMock()
        mock_response.content = '{"invalid": "format"}'
        model_service_with_mock_model.model.invoke = AsyncMock(
            return_value=mock_response
        )

        # Mock validate_verification_response to raise ValidationError
        with patch(
            "arklex.orchestrator.NLU.services.model_service.validate_verification_response"
        ) as mock_validate:
            mock_validate.side_effect = ValidationError("Invalid format")

            with pytest.raises(ValidationError) as exc_info:
                await model_service_with_mock_model.verify_slots(
                    "test text", {"test_slot": "test_value"}
                )

            assert "Invalid verification response format" in str(exc_info.value)

    def test_process_intent_with_empty_attributes(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test _process_intent with empty attributes."""
        intent_k = "test_intent"
        intent_v = [{"attribute": {"definition": "", "sample_utterances": []}}]
        count = 1
        idx2intents_mapping = {}

        def_str, ex_str, choice_str, new_count = (
            model_service_with_mock_model._process_intent(
                intent_k, intent_v, count, idx2intents_mapping
            )
        )

        assert new_count == 2  # count + 1 intent
        assert def_str == ""  # No definition
        assert ex_str == ""  # No exemplars
        assert "1) test_intent" in choice_str  # Fixed expectation

    def test_format_slot_input_with_dict_items(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test format_slot_input with dict items."""
        slots = [
            {
                "name": "test_slot",
                "type": "object",
                "description": "Test description",
                "required": True,
                "items": {
                    "type": "object",
                    "properties": {
                        "key1": {"type": "string"},
                        "key2": {"type": "number"},
                    },
                },
            }
        ]
        context = "test context"

        user_prompt, system_prompt = model_service_with_mock_model.format_slot_input(
            slots, context
        )

        # The actual implementation doesn't include property details in the prompt
        assert "test_slot" in user_prompt
        assert "Test description" in user_prompt
        assert "object" in user_prompt
        assert "required" in user_prompt

    def test_format_slot_input_with_object_items(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test format_slot_input with object items."""
        slots = [
            {
                "name": "test_slot",
                "type": "array",
                "description": "Test description",
                "required": True,
                "items": {
                    "type": "object",
                    "properties": {
                        "prop1": {"type": "string"},
                        "prop2": {"type": "integer"},
                    },
                },
            }
        ]
        context = "test context"

        user_prompt, system_prompt = model_service_with_mock_model.format_slot_input(
            slots, context
        )

        # The actual implementation doesn't include property details in the prompt
        assert "test_slot" in user_prompt
        assert "Test description" in user_prompt
        assert "array" in user_prompt
        assert "required" in user_prompt

    def test_dummy_model_service_get_response_override(
        self, dummy_config: dict[str, Any]
    ) -> None:
        """Test DummyModelService get_response override."""
        dummy_service = DummyModelService(dummy_config)

        result = dummy_service.get_response("test prompt")

        # The actual DummyModelService returns a different format
        assert "test prompt" in result or "1) others" in result

    def test_initialize_model_with_exception(
        self, model_config: dict[str, Any]
    ) -> None:
        """Test _initialize_model when ModelConfig.get_model_instance raises an exception."""
        with patch(
            "arklex.orchestrator.NLU.services.model_service.ModelConfig.get_model_instance"
        ) as mock_get_model:
            mock_get_model.side_effect = Exception("Model initialization failed")

            with pytest.raises(ModelError) as exc_info:
                ModelService(model_config)

            assert "Failed to initialize model" in str(exc_info.value)

    def test_process_intent_with_list_intents(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test _process_intent with list of intents (else branch)."""
        intent_k = "test_intent"
        intent_v = [
            {
                "attribute": {
                    "definition": "Test definition",
                    "sample_utterances": ["test1", "test2"],
                }
            },
            {
                "attribute": {
                    "definition": "Test definition 2",
                    "sample_utterances": ["test3", "test4"],
                }
            },
        ]
        count = 1
        idx2intents_mapping = {}

        def_str, ex_str, choice_str, new_count = (
            model_service_with_mock_model._process_intent(
                intent_k, intent_v, count, idx2intents_mapping
            )
        )

        assert new_count == 3  # count + 2 intents
        assert "test_intent__<0>" in def_str
        assert "test_intent__<1>" in def_str
        assert "1) test_intent__<0>" in choice_str
        assert "2) test_intent__<1>" in choice_str

    def test_get_response_with_none_model_config(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test get_response with None model_config parameter."""
        # Mock model.invoke to return valid response
        mock_response = MagicMock()
        mock_response.content = "test response"
        model_service_with_mock_model.model.invoke.return_value = mock_response

        result = model_service_with_mock_model.get_response(
            "test prompt", model_config=None
        )

        assert result == "test response"

    def test_get_response_with_empty_response(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test get_response when model returns empty response."""
        # Mock model.invoke to return empty response
        mock_response = MagicMock()
        mock_response.content = None
        model_service_with_mock_model.model.invoke.return_value = mock_response

        with pytest.raises(ValueError) as exc_info:
            model_service_with_mock_model.get_response("test prompt")

        assert "Empty response from model" in str(exc_info.value)

    def test_get_response_with_exception(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test get_response when model.invoke raises an exception."""
        # Mock model.invoke to raise exception
        model_service_with_mock_model.model.invoke.side_effect = Exception(
            "Model error"
        )

        with pytest.raises(ValueError) as exc_info:
            model_service_with_mock_model.get_response("test prompt")

        assert "Failed to get model response" in str(exc_info.value)

    def test_get_json_response_with_json_decode_error(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test get_json_response when JSON parsing fails."""
        # Mock get_response to return invalid JSON
        with patch.object(model_service_with_mock_model, "get_response") as mock_get:
            mock_get.return_value = "invalid json"

            with pytest.raises(ValueError) as exc_info:
                model_service_with_mock_model.get_json_response("test prompt")

            assert "Failed to parse JSON response" in str(exc_info.value)

    def test_get_json_response_with_general_exception(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test get_json_response when get_response raises an exception."""
        # Mock get_response to raise exception
        with patch.object(model_service_with_mock_model, "get_response") as mock_get:
            mock_get.side_effect = Exception("General error")

            with pytest.raises(ValueError) as exc_info:
                model_service_with_mock_model.get_json_response("test prompt")

            assert "Failed to get JSON response" in str(exc_info.value)

    def test_format_slot_input_with_pydantic_model(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test format_slot_input with Pydantic model slots."""

        # Create a mock Pydantic model
        class MockSlotModel:
            def __init__(self) -> None:
                self.name = "test_slot"
                self.type = "string"
                self.description = "Test description"
                self.required = True
                self.items = {}

        slots = [MockSlotModel()]
        context = "test context"

        user_prompt, system_prompt = model_service_with_mock_model.format_slot_input(
            slots, context
        )

        assert "test_slot" in user_prompt
        assert "Test description" in user_prompt
        assert "required" in user_prompt

    def test_format_slot_input_with_enum_values(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test format_slot_input with enum values in items."""
        slots = [
            {
                "name": "test_slot",
                "type": "enum",
                "description": "Test description",
                "required": True,
                "items": {"type": "string", "enum": ["option1", "option2", "option3"]},
            }
        ]
        context = "test context"

        user_prompt, system_prompt = model_service_with_mock_model.format_slot_input(
            slots, context
        )

        assert "option1" in user_prompt
        assert "option2" in user_prompt
        assert "option3" in user_prompt

    def test_format_slot_input_without_enum_values(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test format_slot_input without enum values."""
        slots = [
            {
                "name": "test_slot",
                "type": "string",
                "description": "Test description",
                "required": False,
                "items": {},
            }
        ]
        context = "test context"

        user_prompt, system_prompt = model_service_with_mock_model.format_slot_input(
            slots, context
        )

        assert "test_slot" in user_prompt
        assert "Test description" in user_prompt
        assert "optional" in user_prompt
        assert "enum" not in user_prompt.lower()

    def test_dummy_model_service_format_slot_input_override(
        self, dummy_config: dict[str, Any]
    ) -> None:
        """Test DummyModelService format_slot_input override."""
        dummy_service = DummyModelService(dummy_config)

        slots = [{"name": "test_slot", "type": "string"}]
        context = "test context"

        user_prompt, system_prompt = dummy_service.format_slot_input(slots, context)

        # Should call the formatter function
        assert "test_slot" in user_prompt

    def test_dummy_model_service_process_slot_response_override(
        self, dummy_config: dict[str, Any]
    ) -> None:
        """Test DummyModelService process_slot_response override."""
        dummy_service = DummyModelService(dummy_config)

        # Mock the parent class method
        with patch.object(ModelService, "process_slot_response") as mock_parent:
            mock_parent.return_value = [{"name": "test_slot", "value": "test_value"}]

            result = dummy_service.process_slot_response(
                "test response", [{"name": "test_slot"}]
            )

            # Should call parent method
            mock_parent.assert_called_once_with(
                "test response", [{"name": "test_slot"}]
            )
            assert result == [{"name": "test_slot", "value": "test_value"}]

    def test_dummy_model_service_format_verification_input_override(
        self, dummy_config: dict[str, Any]
    ) -> None:
        """Test DummyModelService format_verification_input override."""
        dummy_service = DummyModelService(dummy_config)

        # Mock the formatter function
        with patch(
            "arklex.orchestrator.NLU.services.model_service.format_verification_input_formatter"
        ) as mock_formatter:
            mock_formatter.return_value = "formatted verification input"

            result = dummy_service.format_verification_input(
                {"name": "test_slot"}, "test history"
            )

            # Should call the formatter function
            mock_formatter.assert_called_once_with(
                {"name": "test_slot"}, "test history"
            )
            assert result == "formatted verification input"

    def test_dummy_model_service_process_verification_response_override(
        self, dummy_config: dict[str, Any]
    ) -> None:
        """Test DummyModelService process_verification_response override."""
        dummy_service = DummyModelService(dummy_config)

        # Mock the parent class method
        with patch.object(ModelService, "process_verification_response") as mock_parent:
            mock_parent.return_value = (True, "Verified")

            result = dummy_service.process_verification_response("test response")

            # Should call parent method
            mock_parent.assert_called_once_with("test response")
            assert result == (True, "Verified")

    def test_model_config_get_model_instance_unsupported_provider(self) -> None:
        """Test ModelConfig.get_model_instance with unsupported provider raises ValueError."""
        from arklex.orchestrator.NLU.services.model_config import ModelConfig

        invalid_config = {
            "llm_provider": "unsupported_provider",
            "model_type_or_path": "test_model",
        }

        with pytest.raises(
            ValueError, match="Unsupported provider: unsupported_provider"
        ):
            ModelConfig.get_model_instance(invalid_config)

    async def test_verify_slots_with_invalid_slots_type(
        self, model_service_with_mock_model: ModelService
    ) -> None:
        """Test verify_slots with invalid slots type (not a dict)."""
        from arklex.utils.exceptions import ValidationError

        text = "test text"
        invalid_slots = "not a dict"  # Invalid type

        with pytest.raises(ValidationError, match="Invalid slots"):
            await model_service_with_mock_model.verify_slots(text, invalid_slots)


class TestModelServiceExtraCoverage:
    async def test_process_text_invalid_input_type(
        self, model_service: ModelService
    ) -> None:
        """Test process_text with invalid input type - covers lines 162-170"""
        # Test with non-string input
        with pytest.raises(ArklexError, match="Operation failed in process_text"):
            await model_service.process_text(123)  # type: ignore

    async def test_predict_intent_empty_response(
        self, model_service: ModelService
    ) -> None:
        """Test predict_intent with empty response - covers lines 484-492"""
        with patch.object(
            model_service.model, "invoke", new_callable=AsyncMock
        ) as mock_invoke:
            # Mock empty response
            mock_response = Mock()
            mock_response.content = None
            mock_invoke.return_value = mock_response

            with pytest.raises(ModelError, match="Empty response from model"):
                await model_service.predict_intent("test text")

    async def test_predict_intent_invalid_json(
        self, model_service: ModelService
    ) -> None:
        """Test predict_intent with invalid JSON response"""
        with patch.object(
            model_service.model, "invoke", new_callable=AsyncMock
        ) as mock_invoke:
            # Mock invalid JSON response
            mock_response = Mock()
            mock_response.content = "invalid json"
            mock_invoke.return_value = mock_response

            with pytest.raises(ModelError, match="Failed to parse model response"):
                await model_service.predict_intent("test text")

    async def test_predict_intent_validation_error(
        self, model_service: ModelService
    ) -> None:
        """Test predict_intent with validation error in response"""
        with patch.object(
            model_service.model, "invoke", new_callable=AsyncMock
        ) as mock_invoke:
            # Mock response that will fail validation
            mock_response = Mock()
            mock_response.content = '{"invalid": "response"}'
            mock_invoke.return_value = mock_response

            with pytest.raises(ArklexError, match="Operation failed in predict_intent"):
                await model_service.predict_intent("test text")

    async def test_fill_slots_invalid_input(self, model_service: ModelService) -> None:
        """Test fill_slots with invalid input"""
        with pytest.raises(ValidationError, match="Invalid input text"):
            await model_service.fill_slots("", "test_intent")

    async def test_verify_slots_invalid_input(
        self, model_service: ModelService
    ) -> None:
        """Test verify_slots with invalid input"""
        with pytest.raises(ValidationError, match="Invalid input text"):
            await model_service.verify_slots("", {"slot1": "value1"})

    def test_get_response_with_note(self, model_service: ModelService) -> None:
        """Test get_response with note parameter - covers line 661"""
        with patch.object(model_service.model, "invoke") as mock_invoke:
            mock_response = Mock()
            mock_response.content = "test response"
            mock_invoke.return_value = mock_response

            result = model_service.get_response("test prompt", note="test note")
            assert result == "test response"

    def test_get_json_response_invalid_json(self, model_service: ModelService) -> None:
        """Test get_json_response with invalid JSON"""
        with patch.object(model_service.model, "invoke") as mock_invoke:
            mock_response = Mock()
            mock_response.content = "invalid json"
            mock_invoke.return_value = mock_response

            with pytest.raises(ValueError, match="Failed to parse JSON response"):
                model_service.get_json_response("test prompt")
