"""Тесты для ClaudeOptionsBuilder и _spec_to_sdk_config — фабрика опций SDK."""

import pytest

pytestmark = pytest.mark.requires_claude_sdk

from cognitia.runtime.options_builder import (
    ClaudeOptionsBuilder,
    _build_sse_config,
    _build_stdio_config,
    _build_url_config,
    _spec_to_sdk_config,
)
from cognitia.skills.types import McpServerSpec


class TestSpecToSdkConfig:
    """Конвертация McpServerSpec в SDK-совместимый dict."""

    def test_url_transport(self) -> None:
        """URL transport → Streamable HTTP (type=http для SDK)."""
        spec = McpServerSpec(name="iss", transport="url", url="https://example.com/mcp")
        result = _spec_to_sdk_config(spec)
        assert result == {"type": "http", "url": "https://example.com/mcp"}

    def test_http_transport(self) -> None:
        """HTTP transport → аналогично url (Streamable HTTP)."""
        spec = McpServerSpec(name="iss", transport="http", url="https://example.com/mcp")
        result = _spec_to_sdk_config(spec)
        assert result == {"type": "http", "url": "https://example.com/mcp"}

    def test_sse_transport(self) -> None:
        """SSE transport → type: sse + url."""
        spec = McpServerSpec(name="iss", transport="sse", url="https://example.com/sse")
        result = _spec_to_sdk_config(spec)
        assert result == {"type": "sse", "url": "https://example.com/sse"}

    def test_stdio_transport_minimal(self) -> None:
        """STDIO transport → type: stdio + command."""
        spec = McpServerSpec(name="local", transport="stdio", command="/usr/bin/tool")
        result = _spec_to_sdk_config(spec)
        assert result["type"] == "stdio"
        assert result["command"] == "/usr/bin/tool"
        assert "args" not in result

    def test_stdio_transport_with_args_and_env(self) -> None:
        """STDIO transport с args и env."""
        spec = McpServerSpec(
            name="local", transport="stdio", command="node",
            args=["server.js", "--port=3000"],
            env={"NODE_ENV": "production"},
        )
        result = _spec_to_sdk_config(spec)
        assert result["command"] == "node"
        assert result["args"] == ["server.js", "--port=3000"]
        assert result["env"] == {"NODE_ENV": "production"}

    def test_unknown_transport_defaults_to_url(self) -> None:
        """Неизвестный transport → fallback на url-конфиг."""
        spec = McpServerSpec(name="x", transport="url", url="http://fallback")
        result = _spec_to_sdk_config(spec)
        assert "url" in result

    def test_missing_url_returns_empty_string(self) -> None:
        """Без url → пустая строка."""
        spec = McpServerSpec(name="x", transport="url")
        result = _spec_to_sdk_config(spec)
        assert result["url"] == ""


class TestBuildUrlConfig:
    """Отдельные builder-функции."""

    def test_build_url(self) -> None:
        spec = McpServerSpec(name="t", url="http://test")
        assert _build_url_config(spec) == {"type": "http", "url": "http://test"}

    def test_build_sse(self) -> None:
        spec = McpServerSpec(name="t", url="http://test")
        assert _build_sse_config(spec) == {"type": "sse", "url": "http://test"}

    def test_build_stdio(self) -> None:
        spec = McpServerSpec(name="t", command="cmd")
        result = _build_stdio_config(spec)
        assert result["type"] == "stdio"
        assert result["command"] == "cmd"


class TestClaudeOptionsBuilder:
    """ClaudeOptionsBuilder — сборка ClaudeAgentOptions."""

    def test_build_basic(self) -> None:
        """Базовая сборка с default model policy."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(
            role_id="coach",
            system_prompt="Ты — Freedom",
        )
        assert opts.model == "sonnet"
        assert opts.system_prompt == "Ты — Freedom"

    def test_build_with_mcp_servers(self) -> None:
        """Сборка с MCP-серверами."""
        builder = ClaudeOptionsBuilder()
        servers = {
            "iss": McpServerSpec(name="iss", url="http://iss.test"),
            "fin": McpServerSpec(name="fin", url="http://fin.test"),
        }
        opts = builder.build(
            role_id="coach",
            system_prompt="test",
            mcp_servers=servers,
        )
        assert "iss" in opts.mcp_servers
        assert "fin" in opts.mcp_servers

    def test_build_with_custom_model_policy(self) -> None:
        """Пользовательская model policy."""
        from cognitia.runtime.model_policy import ModelPolicy

        policy = ModelPolicy(
            default_model="custom-sonnet",
            escalation_model="custom-opus",
            escalate_roles={"strategy_planner"},
        )
        builder = ClaudeOptionsBuilder(model_policy=policy)
        opts = builder.build(role_id="coach", system_prompt="test")
        assert opts.model == "custom-sonnet"

        opts_escalated = builder.build(
            role_id="strategy_planner", system_prompt="test"
        )
        assert opts_escalated.model == "custom-opus"

    def test_build_with_tool_failure_escalation(self) -> None:
        """Эскалация модели из-за ошибок tools."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(
            role_id="coach", system_prompt="test",
            tool_failure_count=10,
        )
        assert opts.model == "opus"

    def test_build_permission_mode_default(self) -> None:
        """По умолчанию permission_mode = bypassPermissions."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(role_id="coach", system_prompt="test")
        assert opts.permission_mode == "bypassPermissions"

    def test_build_permission_mode_override(self) -> None:
        """Можно явно задать permission_mode."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(
            role_id="coach",
            system_prompt="test",
            permission_mode="plan",
        )
        assert opts.permission_mode == "plan"

    def test_build_with_cwd(self) -> None:
        """cwd передаётся в опции."""
        builder = ClaudeOptionsBuilder(cwd="/tmp/test")
        opts = builder.build(role_id="coach", system_prompt="test")
        assert opts.cwd == "/tmp/test"

    def test_default_setting_sources_empty(self) -> None:
        """По умолчанию setting_sources=[] — не читаем CLAUDE.md и .claude/settings.json.

        CLAUDE.md содержит developer-facing инструкции (архитектура, команды),
        которые конфликтуют с ролью финансового коуча из system_prompt.
        """
        builder = ClaudeOptionsBuilder()
        opts = builder.build(role_id="coach", system_prompt="test")
        assert opts.setting_sources == []

    def test_explicit_setting_sources_override(self) -> None:
        """Явно переданные setting_sources имеют приоритет."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(
            role_id="coach",
            system_prompt="test",
            setting_sources=["project", "user"],
        )
        assert opts.setting_sources == ["project", "user"]

    def test_override_model_has_priority(self) -> None:
        """override_model имеет приоритет над ModelPolicy."""
        builder = ClaudeOptionsBuilder(override_model="custom-model-v2")
        opts = builder.build(role_id="coach", system_prompt="test")
        assert opts.model == "custom-model-v2"

    def test_build_with_max_thinking_tokens(self) -> None:
        """max_thinking_tokens передаётся в ClaudeAgentOptions."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(
            role_id="coach", system_prompt="test",
            max_thinking_tokens=16000,
        )
        assert opts.max_thinking_tokens == 16000

    def test_build_with_sandbox(self) -> None:
        """sandbox настройки передаются в ClaudeAgentOptions."""
        builder = ClaudeOptionsBuilder()
        sandbox = {"enabled": True, "autoAllowBashIfSandboxed": True}
        opts = builder.build(
            role_id="coach", system_prompt="test",
            sandbox=sandbox,
        )
        assert opts.sandbox == sandbox

    def test_build_with_env(self) -> None:
        """env переменные передаются в ClaudeAgentOptions."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(
            role_id="coach", system_prompt="test",
            env={"MY_VAR": "value"},
        )
        assert opts.env == {"MY_VAR": "value"}

    def test_build_default_env_empty(self) -> None:
        """По умолчанию env — пустой dict."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(role_id="coach", system_prompt="test")
        assert opts.env == {}

    def test_build_with_agents(self) -> None:
        """agents определения передаются в ClaudeAgentOptions."""
        from claude_agent_sdk import AgentDefinition

        builder = ClaudeOptionsBuilder()
        agents = {
            "researcher": AgentDefinition(
                description="Research agent",
                prompt="You are a researcher",
            ),
        }
        opts = builder.build(
            role_id="coach", system_prompt="test",
            agents=agents,
        )
        assert opts.agents is not None
        assert "researcher" in opts.agents

    def test_build_with_output_format(self) -> None:
        """output_format (structured output) передаётся в ClaudeAgentOptions."""
        builder = ClaudeOptionsBuilder()
        schema = {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {"answer": {"type": "string"}},
            },
        }
        opts = builder.build(
            role_id="coach", system_prompt="test",
            output_format=schema,
        )
        assert opts.output_format == schema

    def test_build_default_output_format_none(self) -> None:
        """По умолчанию output_format = None."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(role_id="coach", system_prompt="test")
        assert opts.output_format is None

    def test_build_with_continue_conversation(self) -> None:
        """continue_conversation передаётся в ClaudeAgentOptions."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(
            role_id="coach", system_prompt="test",
            continue_conversation=True,
        )
        assert opts.continue_conversation is True

    def test_build_default_continue_conversation_false(self) -> None:
        """По умолчанию continue_conversation = False."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(role_id="coach", system_prompt="test")
        assert opts.continue_conversation is False

    def test_build_with_resume(self) -> None:
        """resume (session_id) передаётся в ClaudeAgentOptions."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(
            role_id="coach", system_prompt="test",
            resume="session-abc-123",
        )
        assert opts.resume == "session-abc-123"

    def test_build_with_fork_session(self) -> None:
        """fork_session передаётся в ClaudeAgentOptions."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(
            role_id="coach", system_prompt="test",
            fork_session=True,
        )
        assert opts.fork_session is True

    def test_build_with_betas(self) -> None:
        """betas (1M context) передаётся в ClaudeAgentOptions."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(
            role_id="coach", system_prompt="test",
            betas=["context-1m-2025-08-07"],
        )
        assert opts.betas == ["context-1m-2025-08-07"]

    def test_build_default_betas_empty(self) -> None:
        """По умолчанию betas = []."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(role_id="coach", system_prompt="test")
        assert opts.betas == []

    def test_build_with_plugins(self) -> None:
        """plugins передаются в ClaudeAgentOptions."""
        builder = ClaudeOptionsBuilder()
        plugins = [{"type": "local", "path": "/path/to/plugin"}]
        opts = builder.build(
            role_id="coach", system_prompt="test",
            plugins=plugins,
        )
        assert opts.plugins == plugins

    def test_build_with_include_partial_messages(self) -> None:
        """include_partial_messages передаётся в ClaudeAgentOptions."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(
            role_id="coach", system_prompt="test",
            include_partial_messages=True,
        )
        assert opts.include_partial_messages is True

    def test_build_with_enable_file_checkpointing(self) -> None:
        """enable_file_checkpointing передаётся в ClaudeAgentOptions."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(
            role_id="coach", system_prompt="test",
            enable_file_checkpointing=True,
        )
        assert opts.enable_file_checkpointing is True

    def test_build_with_max_budget_usd(self) -> None:
        """max_budget_usd передаётся в ClaudeAgentOptions."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(
            role_id="coach", system_prompt="test",
            max_budget_usd=5.0,
        )
        assert opts.max_budget_usd == 5.0

    def test_build_with_fallback_model(self) -> None:
        """fallback_model передаётся в ClaudeAgentOptions."""
        builder = ClaudeOptionsBuilder()
        opts = builder.build(
            role_id="coach", system_prompt="test",
            fallback_model="haiku",
        )
        assert opts.fallback_model == "haiku"

    def test_build_with_hooks(self) -> None:
        """hooks передаются в ClaudeAgentOptions."""
        from claude_agent_sdk import HookMatcher

        builder = ClaudeOptionsBuilder()

        async def my_hook(input_data, tool_use_id, context):
            return {"continue_": True}

        hooks = {
            "PreToolUse": [HookMatcher(matcher="Bash", hooks=[my_hook])],
        }
        opts = builder.build(
            role_id="coach", system_prompt="test",
            hooks=hooks,
        )
        assert opts.hooks is not None
        assert "PreToolUse" in opts.hooks
