"""PM Agent router - routes discussion between participant Agents."""

from __future__ import annotations

import json
from typing import TYPE_CHECKING

from pydantic import ValidationError

from src.agent import format_scratchpad_summary
from src.models import AgentConfig, MeetingState, PMDecision

if TYPE_CHECKING:
    from src.llm_client import LLMClient

_MAX_RETRIES = 3


def build_pm_system_prompt(available_agents: dict[str, AgentConfig]) -> str:
    """Build the PM Agent's system prompt with available agents listed.

    Args:
        available_agents: Mapping of agent name to AgentConfig.

    Returns:
        A fully-formed system prompt string for the PM LLM call.
    """
    agent_list = "\n".join(
        f"- {name}: {config.role}" for name, config in available_agents.items()
    )

    return f"""You are a meeting facilitator (PM). Your job is to:
1. Analyze the current whiteboard state
2. Decide which Agent should speak next, or if the meeting should end
3. Output ONLY valid JSON matching the schema below

Available Agents:
{agent_list}

You MUST output ONLY a JSON object with these fields:
- "analysis": brief analysis of current whiteboard state
- "next_action": either "CALL_AGENT" or "FINISH"
- "target_agent": name of the agent to call (required if CALL_AGENT)
- "prompt_for_agent": specific question for that agent (required if CALL_AGENT)
- "final_report": complete Markdown meeting report (required if FINISH)

Rules:
- Call each agent at least once before finishing, if possible.
- When all perspectives are covered, output FINISH with a comprehensive report.
- The target_agent MUST be one of the available agents listed above.
- Output ONLY the JSON object, no other text."""


def run_pm(
    state: MeetingState,
    available_agents: dict[str, AgentConfig],
    client: LLMClient,
) -> PMDecision:
    """Run PM Agent to get the next routing decision.

    Calls the LLM with the current whiteboard state. Retries up to
    _MAX_RETRIES times when the response is invalid JSON, fails Pydantic
    validation, or references a non-existent agent. Each retry appends an
    error-feedback message so the LLM can self-correct.

    Args:
        state: Current MeetingState containing topic, scratchpad, and round.
        available_agents: Mapping of agent name to AgentConfig.
        client: Any object satisfying the LLMClient Protocol.

    Returns:
        A validated PMDecision instance.

    Raises:
        RuntimeError: When all retry attempts are exhausted without a valid decision.
    """
    system_prompt = build_pm_system_prompt(available_agents)
    scratchpad_text = format_scratchpad_summary(state.scratchpad)

    user_content = (
        f"## Meeting Topic\n\n{state.topic}\n\n"
        f"## Current Whiteboard (Round {state.current_round})\n\n{scratchpad_text}"
    )

    messages: list[dict[str, str]] = [{"role": "user", "content": user_content}]
    last_error = ""

    for attempt in range(_MAX_RETRIES):
        if last_error:
            messages.append({
                "role": "user",
                "content": f"Your previous response was invalid: {last_error}. Please output ONLY valid JSON.",
            })

        raw_response = client.chat(system=system_prompt, messages=messages)

        try:
            # Strip potential markdown code fences
            cleaned = raw_response.strip()
            if cleaned.startswith("```"):
                cleaned = cleaned.split("\n", 1)[1] if "\n" in cleaned else cleaned
                if cleaned.endswith("```"):
                    cleaned = cleaned[:-3]
                cleaned = cleaned.strip()

            data = json.loads(cleaned)
            decision = PMDecision(**data)

            # Validate target_agent exists in available_agents
            if (
                decision.next_action == "CALL_AGENT"
                and decision.target_agent not in available_agents
            ):
                last_error = (
                    f"target_agent '{decision.target_agent}' is not available. "
                    f"Choose from: {', '.join(available_agents.keys())}"
                )
                continue

            return decision

        except (json.JSONDecodeError, ValidationError) as exc:
            last_error = str(exc)
            continue

    raise RuntimeError(
        f"Failed to get valid PM decision after {_MAX_RETRIES} attempts. "
        f"Last error: {last_error}"
    )
