"""Local delegated-agent runner built on the shared AgentLoop."""

from __future__ import annotations

from uuid import uuid4

from beaver.engine import AgentLoop
from beaver.engine.providers import ProviderBundle
from beaver.tasks.evidence import EvidenceBuilder

from .models import DelegationEnvelope, NodeRunResult


class LocalAgentRunner:
    """Run delegated agents through the same AgentLoop implementation."""

    def __init__(self, loop: AgentLoop) -> None:
        self.loop = loop

    async def run(
        self,
        envelope: DelegationEnvelope,
        *,
        provider_bundle: ProviderBundle | None = None,
        allow_candidate_generation: bool = False,
        execution_mode: str = "shared_loop",
    ) -> NodeRunResult:
        if provider_bundle is not None and (envelope.agent.model or envelope.agent.provider_name):
            raise ValueError(
                "provider_bundle cannot be combined with AgentDescriptor.model/provider_name; "
                "build a node-specific provider bundle instead."
            )
        child_session_id = self._child_session_id(envelope)
        target_loop = self.loop
        if execution_mode == "isolated_loop":
            target_loop = AgentLoop(profile=self.loop.profile, loader=self.loop.loader)
        runner = (
            target_loop.process_direct
            if execution_mode == "isolated_loop"
            else (self.loop.submit_direct if self.loop.is_running else self.loop.process_direct)
        )
        result = await runner(
            envelope.task,
            session_id=child_session_id,
            parent_session_id=envelope.parent_session_id,
            source=f"team:{envelope.agent.name}",
            title=envelope.agent.role or envelope.agent.name,
            execution_context=self._execution_context(envelope),
            skill_selection_context=self._skill_selection_context(envelope),
            model=envelope.agent.model,
            provider_name=envelope.agent.provider_name,
            provider_bundle=provider_bundle,
            task_id=envelope.parent_task_id,
            task_mode=bool(envelope.parent_task_id),
            pinned_skill_names=envelope.inherited_pinned_skills,
            pinned_skill_contexts=envelope.inherited_pinned_skill_contexts,
            allow_candidate_generation=allow_candidate_generation,
        )
        loaded = target_loop.boot()
        evidence = EvidenceBuilder(loaded.session_manager).build_run_evidence(
            result.session_id,
            result.run_id,
            result.output_text,
            result.finish_reason,
        )
        success = result.finish_reason == "stop"
        return NodeRunResult(
            node_id=envelope.node_id or envelope.agent.name,
            success=success,
            output_text=result.output_text,
            run_id=result.run_id,
            session_id=result.session_id,
            finish_reason=result.finish_reason,
            error=None if success else (result.output_text or result.finish_reason),
            evidence=evidence,
        )

    @staticmethod
    def _child_session_id(envelope: DelegationEnvelope) -> str:
        node = envelope.node_id or envelope.agent.name or "node"
        return f"{envelope.parent_session_id}:team:{node}:{uuid4().hex[:8]}"

    @staticmethod
    def _execution_context(envelope: DelegationEnvelope) -> str:
        sections: list[str] = []
        if envelope.parent_task_id:
            sections.append(f"Parent task ID: {envelope.parent_task_id}")
        if envelope.parent_run_id:
            sections.append(f"Parent run ID: {envelope.parent_run_id}")
        sections.append("Delegated worker: generic task sub-agent. Follow active pinned skills as the primary guidance.")
        if envelope.agent.system_prompt:
            sections.append(f"Additional delegated instructions:\n{envelope.agent.system_prompt}")
        if envelope.constraints:
            sections.append("Constraints:\n" + "\n".join(f"- {item}" for item in envelope.constraints))
        if envelope.expected_output:
            sections.append(f"Expected output:\n{envelope.expected_output}")
        if envelope.dependency_outputs:
            rendered = "\n\n".join(
                f"Dependency {node_id} output:\n{output}"
                for node_id, output in envelope.dependency_outputs.items()
            )
            sections.append("Dependency outputs:\n" + rendered)
        if envelope.inherited_pinned_skills:
            sections.append("Pinned inherited skills:\n" + "\n".join(f"- {item}" for item in envelope.inherited_pinned_skills))
        if envelope.inherited_pinned_skill_contexts:
            sections.append(
                "Ephemeral pinned guidance:\n"
                + "\n".join(f"- {item.name} ({item.version})" for item in envelope.inherited_pinned_skill_contexts)
            )
        return "\n\n".join(sections)

    @staticmethod
    def _skill_selection_context(envelope: DelegationEnvelope) -> str:
        sections: list[str] = []
        if envelope.parent_task_id:
            sections.append(f"Parent task ID:\n{envelope.parent_task_id}")
        sections.append(f"Node task:\n{envelope.task}")
        sections.append("Execution phase:\nteam_node")
        if envelope.agent.role:
            sections.append(f"Agent role:\n{envelope.agent.role}")
        skill_query = envelope.agent.metadata.get("skill_query")
        if skill_query:
            sections.append(f"Skill query:\n{skill_query}")
        required_capabilities = envelope.agent.metadata.get("required_capabilities")
        if required_capabilities:
            if isinstance(required_capabilities, list):
                rendered = "\n".join(f"- {item}" for item in required_capabilities)
            else:
                rendered = str(required_capabilities)
            sections.append(f"Required capabilities:\n{rendered}")
        if envelope.constraints:
            sections.append("Constraints:\n" + "\n".join(f"- {item}" for item in envelope.constraints))
        if envelope.expected_output:
            sections.append(f"Expected output:\n{envelope.expected_output}")
        if envelope.inherited_pinned_skills:
            sections.append(
                "Pinned inherited skills (must be injected separately; use as strong context):\n"
                + "\n".join(f"- {item}" for item in envelope.inherited_pinned_skills)
            )
        if envelope.dependency_outputs:
            rendered = "\n\n".join(
                f"Dependency {node_id} output:\n{output[:800]}"
                for node_id, output in envelope.dependency_outputs.items()
            )
            sections.append("Dependency outputs:\n" + rendered)
        sections.append(
            "Skill selection instruction:\n"
            "Select published skills for this delegated node. "
            "If no published skill matches, return [] and let the node continue without skills."
        )
        return "\n\n".join(sections)
