"""Direct tests for file-based agent config loading."""
from __future__ import annotations

import textwrap

import pytest

from logos_adk.agent import Agent
from logos_adk.agent_registry import agent_class_registry, agent_factory_registry
from logos_adk.config import AgentConfig
from logos_adk.config_loader import build_agent_config, load_agent_config, load_agent_from_config
from logos_adk.errors import ConfigError, ToolNotFoundError
from logos_adk.memory import SessionMemory
from logos_adk.observe import ConsoleExporter
from logos_adk.tools import tool
from logos_adk.tools.registry import tool_registry


@pytest.fixture(autouse=True)
def _clear_tool_registry():
    tool_registry.clear()
    yield
    tool_registry.clear()


@pytest.fixture(autouse=True)
def _clear_agent_class_registry():
    agent_class_registry.clear()
    agent_factory_registry.clear()
    yield
    agent_class_registry.clear()
    agent_factory_registry.clear()


class RegistryLoadedAgent(Agent):
    pass


class ImportPathAgent(Agent):
    pass


class ModuleLoadedAgent(Agent):
    pass


class FactoryLoadedAgent(Agent):
    pass


class NoArgFactoryAgent(Agent):
    pass


def _register_loader_tools():
    @tool
    async def loader_search(query: str) -> str:
        return query


def registry_agent_factory(config: AgentConfig) -> Agent:
    return FactoryLoadedAgent(config.name)


def import_path_agent_factory(config: AgentConfig) -> Agent:
    return FactoryLoadedAgent(config.name)


def module_agent_factory(config: AgentConfig) -> Agent:
    return FactoryLoadedAgent(config.name)


def noarg_agent_factory() -> Agent:
    return NoArgFactoryAgent("noarg")


def keyword_only_agent_factory(*, config: AgentConfig) -> Agent:
    return FactoryLoadedAgent(config.name)


def invalid_named_agent_factory(settings: AgentConfig) -> Agent:
    return FactoryLoadedAgent(settings.name)


def invalid_two_arg_agent_factory(config: AgentConfig, extra: str) -> Agent:
    return FactoryLoadedAgent(config.name)


def test_load_agent_from_config_builds_agent(tmp_path):
    _register_loader_tools()
    config_path = tmp_path / "agent.yaml"
    config_path.write_text(
        textwrap.dedent(
            """
            name: loader_bot
            model: claude-sonnet
            system_prompt: You are loaded from YAML
            tools:
              - loader_search
            memory: session
            observe: console
            """
        ).strip()
    )

    agent = load_agent_from_config(str(config_path), agent_cls=Agent)

    assert isinstance(agent, Agent)
    assert agent.name == "loader_bot"
    assert agent._config.model == "claude-sonnet"
    assert agent._config.system_prompt == "You are loaded from YAML"
    assert [tool.name for tool in agent._config.tools] == ["loader_search"]
    assert isinstance(agent._config.memories[0], SessionMemory)
    assert isinstance(agent._config.observers[0], ConsoleExporter)


def test_load_agent_config_builds_typed_config(tmp_path):
    _register_loader_tools()
    config_path = tmp_path / "agent.yaml"
    config_path.write_text(
        textwrap.dedent(
            """
            name: loader_bot
            model: claude-sonnet
            system_prompt: You are loaded from YAML
            tools:
              - loader_search
            memory: session
            observe: console
            """
        ).strip()
    )

    config = load_agent_config(str(config_path))

    assert isinstance(config, AgentConfig)
    assert config.name == "loader_bot"
    assert config.model == "claude-sonnet"
    assert config.system_prompt == "You are loaded from YAML"
    assert [tool.name for tool in config.tools] == ["loader_search"]
    assert isinstance(config.memories[0], SessionMemory)
    assert isinstance(config.observers[0], ConsoleExporter)


def test_agent_from_definition_uses_typed_config():
    config = AgentConfig(name="typed_bot", model="claude-sonnet", system_prompt="typed")

    agent = Agent.from_definition(config)

    assert isinstance(agent, Agent)
    assert agent.name == "typed_bot"
    assert agent._config is config


def test_load_agent_from_config_resolves_registry_agent_class(tmp_path):
    agent_class_registry.register("loader_custom_agent", RegistryLoadedAgent)
    config_path = tmp_path / "registry_agent.yaml"
    config_path.write_text(
        textwrap.dedent(
            """
            name: registry_bot
            agent_class: loader_custom_agent
            model: mock
            """
        ).strip()
    )

    agent = load_agent_from_config(str(config_path), agent_cls=Agent)

    assert isinstance(agent, RegistryLoadedAgent)
    assert agent._config.agent_class == "loader_custom_agent"


def test_load_agent_from_config_resolves_import_path_agent_class(tmp_path):
    config_path = tmp_path / "import_agent.yaml"
    config_path.write_text(
        textwrap.dedent(
            """
            name: import_bot
            agent_class: tests.test_config_loader.ImportPathAgent
            model: mock
            """
        ).strip()
    )

    agent = load_agent_from_config(str(config_path), agent_cls=Agent)

    assert isinstance(agent, ImportPathAgent)


def test_load_agent_from_config_resolves_agent_module_and_short_class(tmp_path):
    config_path = tmp_path / "module_agent.yaml"
    config_path.write_text(
        textwrap.dedent(
            """
            name: module_bot
            agent_module: tests.test_config_loader
            agent_class: ModuleLoadedAgent
            model: mock
            """
        ).strip()
    )

    agent = load_agent_from_config(str(config_path), agent_cls=Agent)

    assert isinstance(agent, ModuleLoadedAgent)


def test_load_agent_from_config_resolves_registry_agent_factory(tmp_path):
    agent_factory_registry.register("loader_custom_factory", registry_agent_factory)
    config_path = tmp_path / "registry_factory_agent.yaml"
    config_path.write_text(
        textwrap.dedent(
            """
            name: registry_factory_bot
            agent_factory: loader_custom_factory
            model: mock
            """
        ).strip()
    )

    agent = load_agent_from_config(str(config_path), agent_cls=Agent)

    assert isinstance(agent, FactoryLoadedAgent)
    assert agent._config.agent_factory == "loader_custom_factory"


def test_load_agent_from_config_resolves_import_path_agent_factory(tmp_path):
    config_path = tmp_path / "import_factory_agent.yaml"
    config_path.write_text(
        textwrap.dedent(
            """
            name: import_factory_bot
            agent_factory: tests.test_config_loader.import_path_agent_factory
            model: mock
            """
        ).strip()
    )

    agent = load_agent_from_config(str(config_path), agent_cls=Agent)

    assert isinstance(agent, FactoryLoadedAgent)


def test_load_agent_from_config_resolves_agent_module_and_short_factory(tmp_path):
    config_path = tmp_path / "module_factory_agent.yaml"
    config_path.write_text(
        textwrap.dedent(
            """
            name: module_factory_bot
            agent_module: tests.test_config_loader
            agent_factory: module_agent_factory
            model: mock
            """
        ).strip()
    )

    agent = load_agent_from_config(str(config_path), agent_cls=Agent)

    assert isinstance(agent, FactoryLoadedAgent)


def test_load_agent_from_config_accepts_noarg_agent_factory(tmp_path):
    config_path = tmp_path / "noarg_factory_agent.yaml"
    config_path.write_text(
        textwrap.dedent(
            """
            name: noarg_factory_bot
            agent_factory: tests.test_config_loader.noarg_agent_factory
            model: mock
            """
        ).strip()
    )

    agent = load_agent_from_config(str(config_path), agent_cls=Agent)

    assert isinstance(agent, NoArgFactoryAgent)
    assert agent.name == "noarg_factory_bot"


def test_load_agent_from_config_accepts_keyword_only_config_factory(tmp_path):
    config_path = tmp_path / "kw_factory_agent.yaml"
    config_path.write_text(
        textwrap.dedent(
            """
            name: kw_factory_bot
            agent_factory: tests.test_config_loader.keyword_only_agent_factory
            model: mock
            """
        ).strip()
    )

    agent = load_agent_from_config(str(config_path), agent_cls=Agent)

    assert isinstance(agent, FactoryLoadedAgent)
    assert agent.name == "kw_factory_bot"


def test_load_agent_from_config_rejects_factory_with_wrong_parameter_name(tmp_path):
    config_path = tmp_path / "invalid_named_factory_agent.yaml"
    config_path.write_text(
        textwrap.dedent(
            """
            name: invalid_named_factory_bot
            agent_factory: tests.test_config_loader.invalid_named_agent_factory
            model: mock
            """
        ).strip()
    )

    with pytest.raises(ConfigError, match="must name its only parameter 'config'"):
        load_agent_from_config(str(config_path), agent_cls=Agent)


def test_load_agent_from_config_rejects_factory_with_multiple_parameters(tmp_path):
    config_path = tmp_path / "invalid_arity_factory_agent.yaml"
    config_path.write_text(
        textwrap.dedent(
            """
            name: invalid_arity_factory_bot
            agent_factory: tests.test_config_loader.invalid_two_arg_agent_factory
            model: mock
            """
        ).strip()
    )

    with pytest.raises(ConfigError, match="either no arguments or exactly one 'config' argument"):
        load_agent_from_config(str(config_path), agent_cls=Agent)


def test_build_agent_config_rejects_unknown_tool():
    with pytest.raises(ToolNotFoundError, match="nonexistent_tool"):
        build_agent_config({"name": "loader_bot", "tools": ["nonexistent_tool"]})


def test_load_agent_from_config_rejects_unknown_tool(tmp_path):
    config_path = tmp_path / "bad_agent.yaml"
    config_path.write_text(
        textwrap.dedent(
            """
            name: loader_bot
            tools:
              - nonexistent_tool
            """
        ).strip()
    )

    with pytest.raises(ToolNotFoundError, match="nonexistent_tool"):
        load_agent_from_config(str(config_path), agent_cls=Agent)


def test_load_agent_config_rejects_unknown_top_level_field(tmp_path):
    config_path = tmp_path / "bad_agent.yaml"
    config_path.write_text(
        textwrap.dedent(
            """
            name: loader_bot
            unsupported: true
            """
        ).strip()
    )

    with pytest.raises(ConfigError, match="Unknown agent config fields"):
        load_agent_config(str(config_path))
