"""
Test script for LLM Runtime integration.

Run with: uv run python tests/test_llm_runtime.py
"""

import logging

import pytest

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# ─────────────────────────────────────────────────────────────────────────────
# Fixtures
# ─────────────────────────────────────────────────────────────────────────────


@pytest.fixture
def ollama_available():
    """Check if Ollama is available."""
    from mcard.ptr.core.llm.providers import OllamaProvider

    provider = OllamaProvider()
    return provider.validate_connection()


# ─────────────────────────────────────────────────────────────────────────────
# Unit Tests - LLMConfig
# ─────────────────────────────────────────────────────────────────────────────


class TestLLMConfig:
    """Tests for LLMConfig dataclass."""

    def test_default_config(self):
        """Test default configuration values."""
        from mcard.ptr.core.llm import LLMConfig

        config = LLMConfig()

        assert config.provider == "ollama"
        assert config.temperature == 0.7
        assert config.max_tokens == 2048
        assert config.effective_model == "gemma3:latest"

    def test_custom_config(self):
        """Test custom configuration."""
        from mcard.ptr.core.llm import LLMConfig

        config = LLMConfig(
            provider="ollama",
            model="llama3:latest",
            temperature=0.5,
            system_prompt="You are helpful.",
        )

        assert config.model == "llama3:latest"
        assert config.temperature == 0.5
        assert config.system_prompt == "You are helpful."

    def test_config_from_dict(self):
        """Test creating config from dictionary."""
        from mcard.ptr.core.llm import LLMConfig

        data = {
            "provider": "ollama",
            "model": "gemma3:27b",
            "temperature": 0.3,
            "max_tokens": 500,
        }

        config = LLMConfig.from_dict(data)

        assert config.model == "gemma3:27b"
        assert config.temperature == 0.3
        assert config.max_tokens == 500

    def test_config_to_provider_params(self):
        """Test conversion to provider parameters."""
        from mcard.ptr.core.llm import LLMConfig

        config = LLMConfig(model="llama3:latest", temperature=0.5, max_tokens=1000)

        params = config.to_provider_params()

        assert params["model"] == "llama3:latest"
        assert params["temperature"] == 0.5
        assert "options" in params  # Ollama-specific


# ─────────────────────────────────────────────────────────────────────────────
# Unit Tests - OllamaProvider
# ─────────────────────────────────────────────────────────────────────────────


class TestOllamaProvider:
    """Tests for OllamaProvider."""

    def test_provider_initialization(self):
        """Test provider initialization."""
        from mcard.ptr.core.llm.providers import OllamaProvider

        provider = OllamaProvider()

        assert provider.provider_name == "ollama"
        assert "localhost" in provider.base_url

    def test_custom_base_url(self):
        """Test custom base URL."""
        from mcard.ptr.core.llm.providers import OllamaProvider

        provider = OllamaProvider(base_url="http://custom:11434")

        assert provider.base_url == "http://custom:11434"

    @pytest.mark.skipif(
        not pytest.importorskip("urllib.request"), reason="urllib not available"
    )
    def test_validate_connection_when_unavailable(self):
        """Test connection validation when Ollama is not running."""
        from mcard.ptr.core.llm.providers import OllamaProvider

        # Use an invalid port to simulate unavailable service
        provider = OllamaProvider(base_url="http://localhost:99999", timeout=1)

        assert provider.validate_connection() == False


# ─────────────────────────────────────────────────────────────────────────────
# Integration Tests - LLMRuntime
# ─────────────────────────────────────────────────────────────────────────────


class TestLLMRuntime:
    """Tests for LLMRuntime executor."""

    def test_runtime_initialization(self):
        """Test runtime initialization."""
        from mcard.ptr.core.llm import LLMRuntime

        runtime = LLMRuntime()

        assert runtime.runtime_name == "llm"
        assert runtime.provider_name == "ollama"

    def test_runtime_factory_registration(self, ollama_available):
        """Test that LLM runtime is registered with RuntimeFactory."""
        from mcard.ptr.core.runtime import RuntimeFactory

        executor = RuntimeFactory.get_executor("llm")

        if ollama_available:
            assert executor is not None
            assert hasattr(executor, "execute")
        else:
            # Should return None if Ollama is not available
            assert executor is None

    @pytest.mark.skipif(
        True,  # Skip by default - requires Ollama
        reason="Requires Ollama to be running",
    )
    def test_simple_completion(self):
        """Test simple text completion."""
        from mcard import MCard
        from mcard.ptr.core.llm import LLMConfig, LLMRuntime

        runtime = LLMRuntime()
        config = LLMConfig(model="gemma3:27b", max_tokens=50, temperature=0.1)

        result = runtime.execute(
            concrete_impl={"llm_config": config.to_dict()},
            target=MCard("Hello, how are you?"),
            context={},
        )

        assert isinstance(result, str)
        assert not result.startswith("Error:")


# ─────────────────────────────────────────────────────────────────────────────
# Integration Tests - Monadic Interface
# ─────────────────────────────────────────────────────────────────────────────


class TestMonadicInterface:
    """Tests for prompt_monad and chat_monad functions."""

    def test_prompt_monad_creation(self):
        """Test prompt_monad creates IO monad."""
        from mcard.ptr.core.llm import prompt_monad
        from mcard.ptr.core.monads import IO

        monad = prompt_monad("Test prompt", temperature=0.5)

        assert isinstance(monad, IO)

    def test_chat_monad_creation(self):
        """Test chat_monad creates IO monad."""
        from mcard.ptr.core.llm import chat_monad
        from mcard.ptr.core.monads import IO

        monad = chat_monad(prompt="Test prompt", system_prompt="You are helpful.")

        assert isinstance(monad, IO)


# ─────────────────────────────────────────────────────────────────────────────
# Live Tests (Require Ollama)
# ─────────────────────────────────────────────────────────────────────────────


@pytest.mark.skipif(
    True,  # Change to False to run live tests
    reason="Live tests require Ollama to be running with models loaded",
)
class TestLiveLLM:
    """Live integration tests requiring Ollama."""

    def test_list_models(self):
        """Test listing available models."""
        from mcard.ptr.core.llm.providers import OllamaProvider

        provider = OllamaProvider()
        result = provider.list_models()

        assert result.is_right()
        models = result.value
        logger.info(f"Available models: {models}")

    def test_simple_chat(self):
        """Test simple chat completion."""
        from mcard.ptr.core.llm import chat_monad

        result = chat_monad(
            prompt="Say 'Hello World' and nothing else.",
            system_prompt="You follow instructions exactly.",
            model="gemma3:27b",
            temperature=0.1,
            max_tokens=20,
        ).unsafe_run()

        assert result.is_right()
        logger.info(f"Chat response: {result.value}")

    def test_clm_execution(self):
        """Test CLM execution via runner."""
        from mcard.ptr.runner import CLMRunner

        runner = CLMRunner()
        report = runner.run_file(
            "chapters/chapter_03_llm/question_answer.clm", context={}
        )

        assert report["status"] == "success"
        logger.info(f"CLM result: {report.get('result')}")


# ─────────────────────────────────────────────────────────────────────────────
# Main
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    pytest.main([__file__, "-v", "--tb=short"])
