"""Agent delegation tools for MindRoom agents.

Allows an agent to delegate tasks to other configured agents via tool calls.
The delegated agent runs independently as a one-shot agent and returns its
response as the tool result.
"""

from __future__ import annotations

from dataclasses import replace
from typing import TYPE_CHECKING
from uuid import uuid4

from agno.tools import Toolkit

from mindroom.agent_descriptions import describe_agent
from mindroom.agent_run_context import append_knowledge_availability_enrichment
from mindroom.ai import ResponseTurnContext, ai_response
from mindroom.knowledge import resolve_agent_knowledge_access
from mindroom.logging_config import get_logger
from mindroom.tool_system.runtime_context import ToolRuntimeContext, get_tool_runtime_context, tool_runtime_context

if TYPE_CHECKING:
    from mindroom.config.main import Config
    from mindroom.constants import RuntimePaths
    from mindroom.knowledge.refresh_scheduler import KnowledgeRefreshScheduler
    from mindroom.tool_system.worker_routing import ToolExecutionIdentity

logger = get_logger(__name__)

MAX_DELEGATION_DEPTH = 3


class DelegateTools(Toolkit):
    """Tools that let an agent delegate tasks to other configured agents."""

    def __init__(
        self,
        agent_name: str,
        delegate_to: list[str],
        runtime_paths: RuntimePaths,
        config: Config,
        execution_identity: ToolExecutionIdentity | None = None,
        delegation_depth: int = 0,
        refresh_scheduler: KnowledgeRefreshScheduler | None = None,
    ) -> None:
        self._agent_name = agent_name
        self._delegate_to = delegate_to
        self._runtime_paths = runtime_paths
        self._config = config
        self._execution_identity = execution_identity
        self._delegation_depth = delegation_depth
        self._refresh_scheduler = refresh_scheduler

        super().__init__(
            name="delegate",
            instructions=self._build_instructions(),
            tools=[self.delegate_task],
        )
        self.async_functions["delegate_task"].description = self._build_delegate_task_description()

    def _build_instructions(self) -> str:
        """Build toolkit instructions listing available delegation targets."""
        lines: list[str] = []
        for target_name in self._delegate_to:
            description = describe_agent(target_name, self._config)
            lines.append(description)
        return self._config.render_prompt(
            "DELEGATE_TOOLKIT_INSTRUCTIONS_TEMPLATE",
            agent_descriptions="\n\n".join(lines),
        )

    def _build_delegate_task_description(self) -> str:
        """Build the model-facing function description with this caller's allowlist."""
        available_targets = ", ".join(self._delegate_to)
        return (
            "Delegate a task to one allowed agent and return its response.\n"
            f"Allowed delegate targets for this caller: {available_targets}.\n"
            "Do not use any other agent names.\n"
            "Use this when you need one listed specialist agent to handle a specific subtask.\n"
            "The delegated agent runs independently with no shared conversation history."
        )

    async def delegate_task(self, agent_name: str, task: str) -> str:
        """Delegate a task to one allowed agent and return its response.

        The runtime-generated tool description lists caller-specific allowed
        targets and model guidance.

        Args:
            agent_name: Name of the agent to delegate to (must be one of your configured targets).
            task: The task description or prompt to send to the agent.

        Returns:
            The delegated agent's response, or an error message if delegation failed.

        """
        if agent_name not in self._delegate_to:
            available = ", ".join(self._delegate_to)
            return (
                f"Cannot delegate to '{agent_name}'. Available agents: {available}. "
                f"Run agents_list to inspect can_delegate flags."
            )

        if not task or not task.strip():
            return "Cannot delegate an empty task. Please provide a task description."

        try:
            session_id = f"delegate:{self._agent_name}:{agent_name}:{uuid4()}"
            execution_identity = (
                replace(self._execution_identity, agent_name=agent_name, session_id=session_id)
                if self._execution_identity is not None
                else None
            )

            knowledge_resolution = resolve_agent_knowledge_access(
                agent_name,
                self._config,
                self._runtime_paths,
                refresh_scheduler=self._refresh_scheduler,
                execution_identity=execution_identity,
            )
            transient_enrichment_items = append_knowledge_availability_enrichment(
                (),
                knowledge_resolution.unavailable,
            )
            logger.info(
                "Delegating task",
                from_agent=self._agent_name,
                to_agent=agent_name,
                depth=self._delegation_depth + 1,
                task_preview=task[:100],
            )
            runtime_context = get_tool_runtime_context()
            room_id = _resolve_delegated_room_id(
                runtime_context=runtime_context,
                execution_identity=execution_identity,
            )
            delegated_runtime_context = self._build_delegated_runtime_context(
                agent_name=agent_name,
                session_id=session_id,
                room_id=room_id,
                runtime_context=runtime_context,
            )
            delegated_correlation_id = (
                delegated_runtime_context.correlation_id if delegated_runtime_context is not None else None
            )
            turn_ctx = ResponseTurnContext(
                entity_label=agent_name,
                session_id=session_id,
                run_id=None,
                correlation_id=delegated_correlation_id or uuid4().hex,
                reply_to_event_id=None,
                room_id=room_id,
                thread_id=None,
                requester_id=execution_identity.requester_id if execution_identity is not None else None,
                matrix_run_metadata=None,
                transient_enrichment_items=tuple(transient_enrichment_items),
            )
            with tool_runtime_context(delegated_runtime_context):
                response = await ai_response(
                    turn_ctx,
                    prompt=task,
                    runtime_paths=self._runtime_paths,
                    config=self._config,
                    knowledge=knowledge_resolution.knowledge,
                    include_interactive_questions=False,
                    tool_function_filter=(
                        runtime_context.tool_function_filter if runtime_context is not None else None
                    ),
                    execution_identity=execution_identity,
                    delegation_depth=self._delegation_depth + 1,
                    refresh_scheduler=self._refresh_scheduler,
                )
        except Exception as e:
            logger.exception(
                "Delegation failed",
                from_agent=self._agent_name,
                to_agent=agent_name,
                error=str(e),
            )
            return f"Delegation to '{agent_name}' failed: {e}"
        else:
            return response or "Agent completed the task but returned no content."

    def _build_delegated_runtime_context(
        self,
        *,
        agent_name: str,
        session_id: str,
        room_id: str | None,
        runtime_context: ToolRuntimeContext | None,
    ) -> ToolRuntimeContext | None:
        """Return the child tool runtime context for one delegated run."""
        if runtime_context is None:
            return None
        runtime_model = self._config.resolve_runtime_model(
            entity_name=agent_name,
            room_id=room_id,
            thread_id=runtime_context.resolved_thread_id,
            runtime_paths=self._runtime_paths,
        )
        return replace(
            runtime_context,
            agent_name=agent_name,
            active_model_name=runtime_model.model_name,
            target=replace(runtime_context.target, session_id=session_id),
        )


def _resolve_delegated_room_id(
    *,
    runtime_context: ToolRuntimeContext | None,
    execution_identity: ToolExecutionIdentity | None,
) -> str | None:
    """Resolve the room context that should apply to a delegated child run."""
    if runtime_context is not None:
        return runtime_context.room_id
    if execution_identity is not None:
        return execution_identity.room_id
    return None
