"""Tests for ieum.config -- configuration, settings, and model detection.

Covers:
- Settings dataclass creation and provider detection
- Provider detection from model name prefixes
- Model name extraction (strip provider prefix)
- COLORS dict has expected keys
- COMMANDS dict has expected entries
- Settings validation (is_valid_agent_name)
- SessionState basic behaviour
"""

import pytest

from ieum.config import (
    COLORS,
    COMMANDS,
    PROVIDERS,
    SessionState,
    Settings,
    _detect_provider,
    _extract_model_name,
)


# ---------------------------------------------------------------------------
# Provider detection
# ---------------------------------------------------------------------------


class TestDetectProvider:
    """Test provider auto-detection from model name."""

    @pytest.mark.parametrize(
        "model_name,expected",
        [
            ("ollama/llama3", "ollama"),
            ("openai/gpt-4o", "openai"),
            ("anthropic/claude-sonnet-4", "anthropic"),
            ("google/gemini-pro", "google"),
            ("groq/llama-3.1-70b", "groq"),
            ("mistral/mistral-large", "mistral"),
            ("openrouter/anthropic/claude-sonnet-4", "openrouter"),
            ("deepseek/deepseek-chat", "deepseek"),
            ("qwen/qwen-72b", "qwen"),
        ],
    )
    def test_known_providers_detected(self, model_name, expected):
        assert _detect_provider(model_name) == expected

    def test_colon_separator(self):
        assert _detect_provider("ollama:llama3") == "ollama"

    def test_unknown_prefix_returns_none(self):
        assert _detect_provider("llama3") is None

    def test_case_insensitive(self):
        assert _detect_provider("OPENAI/gpt-4o") == "openai"
        assert _detect_provider("Anthropic/claude-3") == "anthropic"


class TestExtractModelName:
    """Test provider prefix stripping."""

    @pytest.mark.parametrize(
        "full_name,expected",
        [
            ("ollama/llama3", "llama3"),
            ("openai/gpt-4o-mini", "gpt-4o-mini"),
            ("anthropic/claude-sonnet-4", "claude-sonnet-4"),
            ("openrouter/anthropic/claude-sonnet-4", "anthropic/claude-sonnet-4"),
            ("llama3", "llama3"),  # no prefix, return as-is
        ],
    )
    def test_extraction(self, full_name, expected):
        assert _extract_model_name(full_name) == expected


# ---------------------------------------------------------------------------
# Settings dataclass
# ---------------------------------------------------------------------------


class TestSettings:
    """Test Settings creation and property methods."""

    def test_default_settings(self):
        s = Settings()
        assert s.project_root is None
        assert s.ollama_base_url is None
        assert s.openai_api_key is None

    def test_has_ollama(self):
        s = Settings(ollama_base_url="http://localhost:11434")
        assert s.has_ollama is True

    def test_has_no_ollama(self):
        s = Settings()
        assert s.has_ollama is False

    def test_has_cloud_llm(self):
        s = Settings(openai_api_key="sk-test")
        assert s.has_cloud_llm is True

    def test_has_no_cloud_llm(self):
        s = Settings()
        assert s.has_cloud_llm is False

    def test_has_any_llm_local(self):
        s = Settings(ollama_base_url="http://localhost:11434")
        assert s.has_any_llm is True
        assert s.has_local_llm is True

    def test_has_any_llm_cloud(self):
        s = Settings(anthropic_api_key="sk-ant-test")
        assert s.has_any_llm is True

    def test_has_provider(self):
        s = Settings(openai_api_key="sk-test")
        assert s.has_provider("openai") is True
        assert s.has_provider("anthropic") is False

    def test_has_tavily(self):
        s = Settings(tavily_api_key="tvly-test")
        assert s.has_tavily is True

    def test_has_no_tavily(self):
        s = Settings()
        assert s.has_tavily is False

    def test_has_langsmith(self):
        s = Settings(langchain_api_key="lsv2-test")
        assert s.has_langsmith is True

    def test_has_langfuse(self):
        s = Settings(langfuse_public_key="pk-test", langfuse_secret_key="sk-test")
        assert s.has_langfuse is True

    def test_has_langfuse_requires_both_keys(self):
        s = Settings(langfuse_public_key="pk-test")
        assert s.has_langfuse is False

    def test_tongyi_alias(self):
        s = Settings(qwen_api_key="qwen-test")
        assert s.has_provider("tongyi") is True


class TestSettingsAgentNameValidation:
    """Test agent name validation for filesystem safety."""

    @pytest.mark.parametrize(
        "name",
        [
            "my-agent",
            "agent_1",
            "Agent Name",
            "test123",
        ],
    )
    def test_valid_names(self, name):
        assert Settings._is_valid_agent_name(name) is True

    @pytest.mark.parametrize(
        "name",
        [
            "",
            "   ",
            "../../../etc/passwd",
            "agent/name",
            "agent\x00name",
        ],
    )
    def test_invalid_names(self, name):
        assert Settings._is_valid_agent_name(name) is False

    def test_get_agent_dir_rejects_invalid(self):
        s = Settings()
        with pytest.raises(ValueError, match="Invalid agent name"):
            s.get_agent_dir("../escape")


# ---------------------------------------------------------------------------
# SessionState
# ---------------------------------------------------------------------------


class TestSessionState:
    """Test mutable session state."""

    def test_default_state(self):
        state = SessionState()
        assert state.auto_approve is False
        assert state.no_splash is False

    def test_auto_approve_toggle(self):
        state = SessionState()
        result = state.toggle_auto_approve()
        assert result is True
        assert state.auto_approve is True
        result = state.toggle_auto_approve()
        assert result is False
        assert state.auto_approve is False

    def test_thread_id_generated(self):
        state = SessionState()
        assert state.thread_id is not None
        assert len(state.thread_id) > 0

    def test_unique_thread_ids(self):
        s1 = SessionState()
        s2 = SessionState()
        assert s1.thread_id != s2.thread_id


# ---------------------------------------------------------------------------
# Module-level constants
# ---------------------------------------------------------------------------


class TestConstants:
    """Test module-level configuration constants."""

    def test_colors_has_expected_keys(self):
        for key in ("primary", "dim", "user", "agent", "thinking", "tool"):
            assert key in COLORS, f"Missing color key: {key}"

    def test_colors_are_hex(self):
        for key, value in COLORS.items():
            assert value.startswith("#"), f"Color '{key}' is not hex: {value}"

    def test_commands_dict_not_empty(self):
        assert len(COMMANDS) > 0

    def test_expected_commands_exist(self):
        for cmd in ("help", "new", "quit", "exit", "model", "skills"):
            assert cmd in COMMANDS, f"Missing command: {cmd}"

    def test_providers_list(self):
        assert "ollama" in PROVIDERS
        assert "openai" in PROVIDERS
        assert "anthropic" in PROVIDERS
        assert "openrouter" in PROVIDERS
