"""Team config parsing and validation."""
from __future__ import annotations

from typing import Any

import yaml

from logos_adk.config import AgentConfig
from logos_adk.config_parser import parse_agent_config_data
from logos_adk.errors import ConfigError
from logos_adk.runtime import Supervisor
from logos_adk.team_config import EventBusConfig, TeamConfig, TeamSubscriptionConfig


_ALLOWED_FIELDS = {
    "name", "type", "agents", "orchestrator", "topology",
    "max_rounds", "supervisor", "process", "manager_role",
    "event_bus", "topic_policy", "subscriptions",
}


def parse_team_config_data(data: Any) -> TeamConfig:
    """Parse a raw YAML mapping into a TeamConfig."""
    if not isinstance(data, dict):
        raise ConfigError("team config must be a mapping")

    unknown = set(data) - _ALLOWED_FIELDS
    if unknown:
        raise ConfigError(f"Unknown team config fields: {', '.join(sorted(unknown))}")

    name = data.get("name")
    if not isinstance(name, str) or not name:
        raise ConfigError("team config requires a non-empty string 'name'")

    cfg_type = data.get("type")
    if cfg_type != "team":
        raise ConfigError(
            "team config requires 'type: team' "
            f"(got {cfg_type!r})"
        )

    # Parse agents as AgentConfigs
    raw_agents = data.get("agents", [])
    if not isinstance(raw_agents, list):
        raise ConfigError("team 'agents' must be a list")

    agent_configs: list[AgentConfig] = []
    for raw_agent in raw_agents:
        if isinstance(raw_agent, dict):
            agent_configs.append(parse_agent_config_data(raw_agent))
        else:
            raise ConfigError(
                f"each agent entry must be a mapping, got {type(raw_agent).__name__}"
            )

    # Parse supervisor
    supervisor: Supervisor | None = None
    raw_sup = data.get("supervisor")
    if raw_sup is not None:
        if not isinstance(raw_sup, dict):
            raise ConfigError("'supervisor' must be a mapping")
        supervisor = Supervisor(
            on_failure=raw_sup.get("on_failure", "restart"),
            max_retries=raw_sup.get("max_retries", 3),
            on_max_retries=raw_sup.get("on_max_retries", "escalate"),
        )

    # Parse topology
    topology = data.get("topology")
    if topology is not None and not isinstance(topology, (str, dict)):
        raise ConfigError("'topology' must be a string or mapping")
    process = data.get("process")
    if process is not None and process not in {"sequential", "hierarchical"}:
        raise ConfigError("'process' must be 'sequential' or 'hierarchical'")
    manager_role = data.get("manager_role")
    if manager_role is not None and not isinstance(manager_role, str):
        raise ConfigError("'manager_role' must be a string")
    event_bus = _parse_event_bus_config(data.get("event_bus"))
    topic_policy = _parse_topic_policy(data.get("topic_policy"))
    subscriptions = _parse_subscriptions(data.get("subscriptions"), known_agents={cfg.name for cfg in agent_configs})

    return TeamConfig(
        name=name,
        agents=agent_configs,
        orchestrator=data.get("orchestrator"),
        topology=topology,
        max_rounds=data.get("max_rounds", 10),
        supervisor=supervisor,
        process=process,
        manager_role=manager_role,
        event_bus=event_bus,
        topic_policy=topic_policy,
        subscriptions=subscriptions,
    )


def load_team_config(path: str) -> TeamConfig:
    """Load a TeamConfig from a YAML file."""
    with open(path) as f:
        data = yaml.safe_load(f)
    return parse_team_config_data(data)


def load_team_from_config(path: str):
    """Load a Team instance from a YAML config file."""
    from logos_adk.team import Team

    return Team.from_definition(load_team_config(path))


def _parse_event_bus_config(raw_event_bus: Any) -> EventBusConfig | None:
    if raw_event_bus is None:
        return None
    if not isinstance(raw_event_bus, dict):
        raise ConfigError("'event_bus' must be a mapping")
    unknown = set(raw_event_bus) - {"backend", "path", "dsn"}
    if unknown:
        raise ConfigError(f"Unknown event_bus fields: {', '.join(sorted(unknown))}")
    backend = raw_event_bus.get("backend", "memory")
    if backend == "postgres":
        backend = "postgresql"
    if backend not in {"memory", "sqlite", "postgresql"}:
        raise ConfigError("'event_bus.backend' must be 'memory', 'sqlite', or 'postgresql'")
    path = raw_event_bus.get("path")
    dsn = raw_event_bus.get("dsn")
    if path is not None and not isinstance(path, str):
        raise ConfigError("'event_bus.path' must be a string")
    if dsn is not None and not isinstance(dsn, str):
        raise ConfigError("'event_bus.dsn' must be a string")
    if backend == "sqlite" and (not isinstance(path, str) or not path):
        raise ConfigError("'event_bus.path' is required when backend is 'sqlite'")
    return EventBusConfig(backend=backend, path=path, dsn=dsn)


def _parse_topic_policy(raw_topic_policy: Any) -> dict[str, list[str]] | None:
    if raw_topic_policy is None:
        return None
    if not isinstance(raw_topic_policy, dict):
        raise ConfigError("'topic_policy' must be a mapping")
    policy: dict[str, list[str]] = {}
    for agent_name, topics in raw_topic_policy.items():
        if not isinstance(agent_name, str) or not agent_name:
            raise ConfigError("'topic_policy' keys must be non-empty strings")
        if not isinstance(topics, list) or not all(isinstance(topic, str) and topic for topic in topics):
            raise ConfigError(f"'topic_policy.{agent_name}' must be a list of non-empty strings")
        policy[agent_name] = list(topics)
    return policy


def _parse_subscriptions(
    raw_subscriptions: Any,
    *,
    known_agents: set[str],
) -> list[TeamSubscriptionConfig]:
    if raw_subscriptions is None:
        return []
    if not isinstance(raw_subscriptions, list):
        raise ConfigError("'subscriptions' must be a list")
    subscriptions: list[TeamSubscriptionConfig] = []
    for index, item in enumerate(raw_subscriptions):
        if not isinstance(item, dict):
            raise ConfigError(f"'subscriptions[{index}]' must be a mapping")
        unknown = set(item) - {"agent", "topic", "handler", "publish_to", "name"}
        if unknown:
            raise ConfigError(
                f"Unknown subscriptions[{index}] fields: {', '.join(sorted(unknown))}"
            )
        agent_name = item.get("agent")
        topic = item.get("topic")
        if not isinstance(agent_name, str) or not agent_name:
            raise ConfigError(f"'subscriptions[{index}].agent' must be a non-empty string")
        if agent_name not in known_agents:
            raise ConfigError(
                f"'subscriptions[{index}].agent' references unknown agent '{agent_name}'"
            )
        if not isinstance(topic, str) or not topic:
            raise ConfigError(f"'subscriptions[{index}].topic' must be a non-empty string")
        handler = item.get("handler")
        if handler is not None and (not isinstance(handler, str) or not handler):
            raise ConfigError(f"'subscriptions[{index}].handler' must be a string")
        publish_to = item.get("publish_to")
        if publish_to is not None and (not isinstance(publish_to, str) or not publish_to):
            raise ConfigError(f"'subscriptions[{index}].publish_to' must be a string")
        name = item.get("name")
        if name is not None and (not isinstance(name, str) or not name):
            raise ConfigError(f"'subscriptions[{index}].name' must be a string")
        subscriptions.append(
            TeamSubscriptionConfig(
                agent_name=agent_name,
                topic=topic,
                handler=handler,
                publish_to=publish_to,
                name=name,
            )
        )
    return subscriptions
