from dataclasses import dataclass, field

from backend.config import get_enterprise_agents
from backend.schemas import schemas


@dataclass
class ResolvedConversationAgents:
    """Request-scoped view of the agent ids a conversation turn is allowed to use.

    The conversation flow first needs a light catalog for validation, selection and
    multimodal routing. Full user-agent payloads are only materialized later, on the
    execution path that actually needs prompts, tools or documents.
    """

    requested_agent_ids: list[str]
    enterprise_agents_by_id: dict[str, dict]
    user_agent_summaries_by_id: dict[str, schemas.AgentRead]
    loaded_user_agents_by_id: dict[str, schemas.AgentRead] = field(default_factory=dict)


def resolve_requested_agents(
    store,
    agent_ids: list[str],
    *,
    current_user: str | None,
) -> ResolvedConversationAgents:
    """Resolve only the agent ids requested for the current turn.

    In agent mode, the frontend already sends the candidate ids. This helper keeps the
    backend scoped to that subset instead of rebuilding the full user-agent catalog.
    """
    if not agent_ids:
        return ResolvedConversationAgents([], {}, {})

    requested_agent_ids = list(dict.fromkeys(agent_ids))
    enterprise_agents = {
        ea["id"]: ea for ea in get_enterprise_agents(current_user) if ea.get("id") in requested_agent_ids
    }
    user_agent_ids = [aid for aid in requested_agent_ids if aid not in enterprise_agents]
    user_agent_summaries = store.get_user_agent_summaries_by_ids(user_agent_ids)
    return ResolvedConversationAgents(
        requested_agent_ids=requested_agent_ids,
        enterprise_agents_by_id=enterprise_agents,
        user_agent_summaries_by_id=user_agent_summaries,
    )


def load_user_agents_full(
    store,
    resolved_agents: ResolvedConversationAgents,
    agent_ids: list[str],
) -> dict[str, schemas.AgentRead]:
    """Load full user-agent payloads for the selected execution subset only.

    The light summaries remain enough for validation and selection. Full details are
    loaded only when runtime code needs prompts, tools or documents.
    """
    user_agent_ids = [
        aid
        for aid in dict.fromkeys(agent_ids)
        if aid in resolved_agents.user_agent_summaries_by_id and aid not in resolved_agents.loaded_user_agents_by_id
    ]
    if not user_agent_ids:
        return resolved_agents.loaded_user_agents_by_id

    resolved_agents.loaded_user_agents_by_id.update(store.get_user_agents_by_ids(user_agent_ids))
    return resolved_agents.loaded_user_agents_by_id


def get_user_agent_full(
    store,
    resolved_agents: ResolvedConversationAgents,
    agent_id: str,
) -> schemas.AgentRead | None:
    """Return one full user-agent payload, loading it on demand if needed."""
    load_user_agents_full(store, resolved_agents, [agent_id])
    return resolved_agents.loaded_user_agents_by_id.get(agent_id)


def get_agent_name(
    agent_id: str,
    *,
    resolved_agents: ResolvedConversationAgents,
    prefer_full_user_agents: bool = False,
) -> str:
    """Return a display name for logs and events without forcing a full catalog load."""
    if enterprise_agent := resolved_agents.enterprise_agents_by_id.get(agent_id):
        return enterprise_agent.get("name") or agent_id

    if prefer_full_user_agents and agent_id in resolved_agents.loaded_user_agents_by_id:
        user_agent = resolved_agents.loaded_user_agents_by_id[agent_id]
    else:
        user_agent = resolved_agents.user_agent_summaries_by_id.get(agent_id)

    if not user_agent:
        return agent_id
    if user_agent.published_version and user_agent.published_version.name:
        return user_agent.published_version.name
    return user_agent.name or "Unnamed Agent"


def missing_agents(agent_ids: list[str], resolved_agents: ResolvedConversationAgents) -> list[str]:
    """Return the ids requested by the client that are not resolvable for this user."""
    existing = set(resolved_agents.enterprise_agents_by_id.keys())
    existing.update(resolved_agents.user_agent_summaries_by_id.keys())
    return [aid for aid in agent_ids if aid not in existing]


def filter_active_agents(
    agent_ids: list[str],
    resolved_agents: ResolvedConversationAgents,
    *,
    current_user: str | None,
    allow_draft: bool = False,
) -> list[str]:
    """Keep only agents that are runnable in the current mode.

    Enterprise agents always stay eligible. User agents require a published snapshot,
    except for the owner's draft-mode flow where the draft itself is executable.
    """
    enterprise_ids = set(resolved_agents.enterprise_agents_by_id.keys())
    filtered = []

    for aid in agent_ids:
        if aid in enterprise_ids:
            filtered.append(aid)
            continue

        agent = resolved_agents.user_agent_summaries_by_id.get(aid)
        if not agent:
            continue

        is_owner = agent.owner == current_user
        if allow_draft and is_owner:
            filtered.append(aid)
        elif agent.published_version:
            filtered.append(aid)

    return filtered
