"""Resolve planner node requirements to registered specialist agents."""

from __future__ import annotations

from dataclasses import replace
from typing import Any, TYPE_CHECKING

from beaver.coordinator.models import AgentDescriptor, ExecutionGraph, ExecutionNode

from .models import AgentMatch, RegisteredAgent, TargetResolutionReport
from .store import AgentRegistry

if TYPE_CHECKING:
    from beaver.tasks.models import TaskRecord


class TargetResolver:
    def __init__(self, registry: AgentRegistry) -> None:
        self.registry = registry

    def resolve_graph(
        self,
        graph: ExecutionGraph,
        *,
        task: "TaskRecord",
        user_message: str,
        attempt_index: int,
    ) -> tuple[ExecutionGraph, list[TargetResolutionReport]]:
        reports: list[TargetResolutionReport] = []
        resolved_nodes: list[ExecutionNode] = []
        for node in graph.nodes:
            descriptor, report = self.resolve_node(
                node,
                task=task,
                user_message=user_message,
                attempt_index=attempt_index,
            )
            resolved_nodes.append(replace(node, agent=descriptor))
            reports.append(report)
        return ExecutionGraph(strategy=graph.strategy, nodes=resolved_nodes), reports

    def resolve_node(
        self,
        node: ExecutionNode,
        *,
        task: "TaskRecord",
        user_message: str,
        attempt_index: int,
    ) -> tuple[AgentDescriptor, TargetResolutionReport]:
        requested_role = (node.agent.role or node.agent.name or node.node_id).strip()
        requested_capabilities = [
            str(item).strip()
            for item in node.agent.metadata.get("requested_capabilities", [])
            if str(item).strip()
        ]
        requested_tags = [
            str(item).strip()
            for item in node.agent.metadata.get("requested_tags", [])
            if str(item).strip()
        ]
        pinned_skills = list(node.inherited_pinned_skills)
        match = self.best_match(
            requested_role=requested_role,
            requested_capabilities=requested_capabilities,
            requested_tags=requested_tags,
            pinned_skills=pinned_skills,
            task_text=" ".join([task.goal, task.description, user_message, node.task]),
        )
        if match is not None and match.score > 0:
            descriptor = match.resolved_descriptor
            descriptor.metadata.update(
                {
                    "node_id": node.node_id,
                    "attempt_index": attempt_index,
                    "requested_role": requested_role,
                    "requested_capabilities": requested_capabilities,
                }
            )
            return descriptor, TargetResolutionReport(
                node_id=node.node_id,
                requested_role=requested_role,
                requested_capabilities=requested_capabilities,
                selected_agent_id=match.agent_id,
                fallback_used=False,
                score=match.score,
                reason="; ".join(match.reasons),
            )
        fallback = AgentDescriptor(
            name=node.agent.name or node.node_id,
            role=node.agent.role,
            system_prompt=node.agent.system_prompt,
            model=node.agent.model,
            provider_name=node.agent.provider_name,
            metadata={
                **node.agent.metadata,
                "node_id": node.node_id,
                "attempt_index": attempt_index,
                "requested_role": requested_role,
                "requested_capabilities": requested_capabilities,
                "resolution": "fallback_ephemeral",
            },
        )
        return fallback, TargetResolutionReport(
            node_id=node.node_id,
            requested_role=requested_role,
            requested_capabilities=requested_capabilities,
            selected_agent_id=None,
            fallback_used=True,
            score=0.0,
            reason="no active registered specialist matched planner requirements",
        )

    def best_match(
        self,
        *,
        requested_role: str,
        requested_capabilities: list[str],
        requested_tags: list[str],
        pinned_skills: list[str],
        task_text: str,
    ) -> AgentMatch | None:
        matches = [
            self._score_agent(
                agent,
                requested_role=requested_role,
                requested_capabilities=requested_capabilities,
                requested_tags=requested_tags,
                pinned_skills=pinned_skills,
                task_text=task_text,
            )
            for agent in self.registry.list_active_agents()
        ]
        matches = [match for match in matches if match.score > 0]
        if not matches:
            return None
        matches.sort(key=lambda item: (item.score, item.resolved_descriptor.metadata.get("priority", 0)), reverse=True)
        return matches[0]

    def _score_agent(
        self,
        agent: RegisteredAgent,
        *,
        requested_role: str,
        requested_capabilities: list[str],
        requested_tags: list[str],
        pinned_skills: list[str],
        task_text: str,
    ) -> AgentMatch:
        score = 0.0
        reasons: list[str] = []
        requested_role_terms = _terms(requested_role)
        capability_terms = _terms(" ".join(requested_capabilities))
        tag_terms = _terms(" ".join(requested_tags))
        skill_terms = _terms(" ".join(pinned_skills))
        task_terms = _terms(task_text)
        agent_role_terms = _terms(agent.role + " " + agent.name + " " + agent.display_name)
        agent_capability_terms = _terms(" ".join(agent.capabilities))
        agent_tag_terms = _terms(" ".join(agent.tags))
        agent_skill_terms = _terms(" ".join(agent.skill_names))
        agent_all_terms = (
            agent_role_terms
            | agent_capability_terms
            | agent_tag_terms
            | agent_skill_terms
            | _terms(agent.description)
        )

        role_hits = requested_role_terms & agent_role_terms
        if role_hits:
            score += 60 + 5 * len(role_hits)
            reasons.append(f"role matched: {', '.join(sorted(role_hits))}")

        capability_hits = capability_terms & agent_capability_terms
        if capability_hits:
            score += 30 + 5 * len(capability_hits)
            reasons.append(f"capabilities matched: {', '.join(sorted(capability_hits))}")

        tag_hits = tag_terms & agent_tag_terms
        if tag_hits:
            score += 10 + 3 * len(tag_hits)
            reasons.append(f"tags matched: {', '.join(sorted(tag_hits))}")

        skill_hits = skill_terms & agent_skill_terms
        if skill_hits:
            score += 25 + 5 * len(skill_hits)
            reasons.append(f"skills matched: {', '.join(sorted(skill_hits))}")

        task_hits = task_terms & agent_all_terms
        if task_hits:
            score += min(20, len(task_hits) * 2)
            reasons.append("task text matched registry profile")

        score += agent.priority / 100.0
        descriptor = agent.to_descriptor()
        descriptor.metadata["priority"] = agent.priority
        return AgentMatch(
            agent_id=agent.agent_id,
            score=round(score, 3),
            reasons=reasons or ["priority fallback"],
            matched_capabilities=sorted(capability_hits),
            resolved_descriptor=descriptor,
        )


def _terms(value: Any) -> set[str]:
    text = str(value or "")
    normalized = "".join(ch.lower() if ch.isalnum() else " " for ch in text)
    return {part for part in normalized.split() if part}
