"""File-based agent config loading."""
from __future__ import annotations

import importlib
import inspect
from typing import Any, TypeVar

import yaml

from logos_adk.agent_registry import agent_class_registry, agent_factory_registry
from logos_adk.config import AgentConfig
from logos_adk.config_parser import parse_agent_config_data
from logos_adk.errors import ConfigError


AgentT = TypeVar("AgentT")


def build_agent_config(data: dict[str, Any]) -> AgentConfig:
    """Build a typed AgentConfig from parsed YAML data."""
    return parse_agent_config_data(data)


def load_agent_config(path: str) -> AgentConfig:
    """Load a typed AgentConfig from a YAML config file."""
    with open(path) as f:
        data = yaml.safe_load(f)
    return build_agent_config(data)


def load_agent_from_config(path: str, *, agent_cls: type[AgentT]) -> AgentT:
    """Load an agent instance from a YAML config file."""
    return build_agent_from_definition(load_agent_config(path), default_cls=agent_cls)


def build_agent_from_definition(config: AgentConfig, *, default_cls: type[AgentT]) -> AgentT:
    """Build an agent instance from a typed AgentConfig."""
    factory = resolve_agent_factory(config)
    if factory is not None:
        return _build_agent_with_factory(factory, config=config)
    resolved_cls = resolve_agent_class(config, default_cls=default_cls)
    return resolved_cls.from_definition(config)


def resolve_agent_factory(config: AgentConfig) -> Any | None:
    """Resolve the Agent factory configured for this AgentConfig."""
    if config.agent_module:
        _import_agent_module(config.agent_module)

    if config.agent_factory is None:
        return None

    registered = agent_factory_registry.get(config.agent_factory)
    if registered is not None:
        return _ensure_agent_factory(registered, label=config.agent_factory)

    if config.agent_module:
        candidate = _load_attribute_from_module(config.agent_module, config.agent_factory)
        if candidate is not None:
            return _ensure_agent_factory(candidate, label=f"{config.agent_module}:{config.agent_factory}")

    candidate = _load_attribute_from_path(config.agent_factory)
    if candidate is not None:
        return _ensure_agent_factory(candidate, label=config.agent_factory)

    raise ConfigError(
        f"Unknown agent_factory '{config.agent_factory}'. Register it in agent_factory_registry "
        "or provide an import path like 'package.module:create_agent'."
    )


def resolve_agent_class(config: AgentConfig, *, default_cls: type[AgentT]) -> type[AgentT]:
    """Resolve the Agent subclass configured for this AgentConfig."""
    if config.agent_module:
        _import_agent_module(config.agent_module)

    if config.agent_class is None:
        return default_cls

    registered = agent_class_registry.get(config.agent_class)
    if registered is not None:
        return _ensure_agent_class(registered, label=config.agent_class)

    if config.agent_module:
        candidate = _load_agent_class_from_module(config.agent_module, config.agent_class)
        if candidate is not None:
            return _ensure_agent_class(candidate, label=f"{config.agent_module}:{config.agent_class}")

    candidate = _load_agent_class_from_path(config.agent_class)
    if candidate is not None:
        return _ensure_agent_class(candidate, label=config.agent_class)

    raise ConfigError(
        f"Unknown agent_class '{config.agent_class}'. Register it in agent_class_registry "
        "or provide an import path like 'package.module:ClassName'."
    )


def _import_agent_module(module_name: str) -> Any:
    try:
        return importlib.import_module(module_name)
    except Exception as exc:
        raise ConfigError(f"Failed to import agent_module '{module_name}': {exc}") from exc


def _load_agent_class_from_module(module_name: str, class_name: str) -> type[Any] | None:
    candidate = _load_attribute_from_module(module_name, class_name)
    return candidate if isinstance(candidate, type) else None


def _load_agent_class_from_path(path: str) -> type[Any] | None:
    candidate = _load_attribute_from_path(path)
    return candidate if isinstance(candidate, type) else None


def _load_attribute_from_module(module_name: str, attr_name: str) -> Any | None:
    module = _import_agent_module(module_name)
    return getattr(module, attr_name, None)


def _load_attribute_from_path(path: str) -> Any | None:
    if ":" in path:
        module_name, attr_name = path.split(":", 1)
    elif "." in path:
        module_name, attr_name = path.rsplit(".", 1)
    else:
        return None
    if not module_name or not attr_name:
        return None
    return _load_attribute_from_module(module_name, attr_name)


def _ensure_agent_class(candidate: type[Any], *, label: str) -> type[Any]:
    from logos_adk.agent import Agent

    if not issubclass(candidate, Agent):
        raise ConfigError(f"Configured agent class '{label}' must inherit from Agent")
    return candidate


def _ensure_agent_factory(candidate: Any, *, label: str) -> Any:
    if not callable(candidate):
        raise ConfigError(f"Configured agent factory '{label}' must be callable")
    return candidate


def _build_agent_with_factory(factory: Any, *, config: AgentConfig) -> Any:
    from logos_adk.agent import Agent

    agent = _invoke_agent_factory(factory, config=config)
    if not isinstance(agent, Agent):
        raise ConfigError(
            f"Configured agent factory '{getattr(factory, '__name__', repr(factory))}' must return an Agent"
        )
    agent._config = config
    agent._apply_policy_config()
    return agent


def _invoke_agent_factory(factory: Any, *, config: AgentConfig) -> Any:
    label = getattr(factory, "__name__", repr(factory))
    try:
        signature = inspect.signature(factory)
    except (TypeError, ValueError) as exc:
        raise ConfigError(
            f"Configured agent factory '{label}' must have an inspectable signature"
        ) from exc

    parameters = list(signature.parameters.values())
    if not parameters:
        return factory()

    if len(parameters) != 1:
        raise ConfigError(
            f"Configured agent factory '{label}' must accept either no arguments or exactly one 'config' argument"
        )

    parameter = parameters[0]
    if parameter.kind not in {
        inspect.Parameter.POSITIONAL_OR_KEYWORD,
        inspect.Parameter.KEYWORD_ONLY,
    }:
        raise ConfigError(
            f"Configured agent factory '{label}' must declare 'config' as a positional-or-keyword or keyword-only parameter"
        )
    if parameter.name != "config":
        raise ConfigError(
            f"Configured agent factory '{label}' must name its only parameter 'config'"
        )

    if parameter.kind is inspect.Parameter.KEYWORD_ONLY:
        return factory(config=config)
    return factory(config)
