"""Colony agent runners — each agent is a configured call to agent_loop()."""

from __future__ import annotations

import random
from pathlib import Path

import structlog

from fliiq.runtime.agent.config import AgentConfig
from fliiq.runtime.agent.loop import AgentResult, agent_loop
from fliiq.runtime.agent.tools import ToolRegistry
from fliiq.runtime.colony import prompts
from fliiq.runtime.colony.models import AgentRole
from fliiq.runtime.colony.state import StateManager
from fliiq.runtime.llm.providers import BaseLLM
from fliiq.runtime.skills.loader import discover_skills

log = structlog.get_logger()

# Tool allowlists per agent role
AGENT_TOOLS: dict[str, set[str]] = {
    "intelligence": {"web_search", "web_fetch", "read_file", "write_file", "list_directory", "todo"},
    "research": {"web_search", "web_fetch", "read_file", "write_file", "list_directory", "grep", "find", "todo"},
    "scout": {"web_search", "web_fetch", "read_file", "write_file", "list_directory", "todo"},
    "qa": {"shell", "read_file", "write_file", "list_directory", "grep", "find", "todo"},
    "governance": {"shell", "read_file", "write_file", "edit_file", "list_directory", "grep", "find", "todo"},
}


async def run_colony_agent(
    role: AgentRole,
    llm: BaseLLM,
    tools: ToolRegistry,
    state: StateManager,
    project_root: Path,
    trigger_context: str,
    cycle_number: int,
    max_iterations: int = 50,
    mission: str = "",
    previous_context: str = "",
) -> AgentResult:
    """Run a single colony agent cycle.

    1. Build system prompt with role identity + state context
    2. Filter tool definitions to agent's allowlist
    3. Construct user message from trigger context
    4. Call agent_loop() in autonomous mode
    5. Return AgentResult
    """
    system = _build_system_prompt(
        role, state, project_root, mission=mission, previous_context=previous_context,
    )

    # Filter tools to only what this agent is allowed
    all_defs = tools.get_tool_definitions()
    allowed = AGENT_TOOLS.get(role.value, set())
    filtered_defs = [d for d in all_defs if d["name"] in allowed]

    user_message = f"Cycle {cycle_number}. {trigger_context}"
    messages = [{"role": "user", "content": user_message}]

    config = AgentConfig(mode="autonomous", max_iterations=max_iterations)

    log.info("colony.agent_start", agent=role.value, cycle=cycle_number)

    result = await agent_loop(
        llm=llm,
        messages=messages,
        tools=tools,
        config=config,
        system=system,
        tool_defs_override=filtered_defs,
    )

    log.info(
        "colony.agent_done",
        agent=role.value,
        cycle=cycle_number,
        iterations=result.iterations,
        stop_reason=result.stop_reason,
    )

    return result


def _build_system_prompt(
    role: AgentRole, state: StateManager, project_root: Path,
    mission: str = "", previous_context: str = "",
) -> str:
    """Build the complete system prompt for an agent based on role."""
    if not isinstance(role, AgentRole):
        msg = f"Unknown agent role: {role}"
        raise ValueError(msg)
    project_summary = _get_project_summary(project_root)
    reflection_context = _get_reflection_context(state, role)
    capabilities_inventory = _get_capabilities_inventory(project_root)

    if role == AgentRole.INTELLIGENCE:
        role_prompt = prompts.intelligence_prompt(
            project_summary=project_summary,
            recent_briefs=state.build_intelligence_context(limit=10),
            reflection_context=reflection_context,
            capabilities_inventory=capabilities_inventory,
        )

    elif role == AgentRole.RESEARCH:
        role_prompt = prompts.research_prompt(
            project_summary=project_summary,
            intel_briefs=state.build_intelligence_context(limit=10),
            existing_proposals=state.build_proposals_context(),
            reflection_context=reflection_context,
            capabilities_inventory=capabilities_inventory,
        )

    elif role == AgentRole.SCOUT:
        persona = random.choice(prompts.SCOUT_PERSONAS)  # noqa: S311
        role_prompt = prompts.scout_prompt(
            project_summary=project_summary,
            intel_briefs=state.build_intelligence_context(limit=5),
            existing_proposals=state.build_proposals_context(),
            capabilities_inventory=capabilities_inventory,
            persona=persona,
            reflection_context=reflection_context,
        )

    elif role == AgentRole.QA:
        from fliiq.runtime.colony.git_ops import get_diff_summary

        try:
            recent_changes = get_diff_summary(project_root)
        except Exception:
            recent_changes = "Could not get diff summary."

        role_prompt = prompts.qa_prompt(
            project_summary=project_summary,
            recent_changes=recent_changes,
            metrics_history=state.build_qa_context(),
            pending_proposals=state.build_proposals_context(status="proposed"),
            reflection_context=reflection_context,
            capabilities_inventory=capabilities_inventory,
        )

    elif role == AgentRole.GOVERNANCE:
        under_review = state.list_proposals(status="proposed") + state.list_proposals(status="under_review")
        votes_lines = []
        for p in under_review:
            votes = state.get_votes_for(p.id)
            if votes:
                for v in votes:
                    votes_lines.append(f"{p.id} — {v.voter}: {v.position} — {v.reasoning[:150]}")

        role_prompt = prompts.governance_prompt(
            project_summary=project_summary,
            proposals_for_review=state.build_proposals_context(status="proposed"),
            votes_summary="\n".join(votes_lines) if votes_lines else "No votes recorded yet.",
            qa_metrics=state.build_qa_context(),
            escalation_policy=prompts.DEFAULT_ESCALATION_POLICY,
            reflection_context=reflection_context,
            code_freeze=False,
            capabilities_inventory=capabilities_inventory,
        )

    else:
        msg = f"Unknown agent role: {role}"
        raise ValueError(msg)

    # Inject mission directive in directed mode
    if mission:
        preamble = prompts.mission_preamble(mission, previous_context=previous_context)
        if role == AgentRole.GOVERNANCE:
            preamble += "\n" + prompts.GOVERNANCE_MISSION_COMPLETION
        return preamble + "\n\n" + role_prompt

    return role_prompt


def _get_project_summary(project_root: Path) -> str:
    """Read README + pyproject.toml metadata for agent context."""
    parts = []

    readme = project_root / "README.md"
    if readme.is_file():
        lines = readme.read_text().splitlines()[:80]
        parts.append("### README (first 80 lines)\n" + "\n".join(lines))

    pyproject = project_root / "pyproject.toml"
    if pyproject.is_file():
        lines = pyproject.read_text().splitlines()[:30]
        parts.append("### pyproject.toml (header)\n" + "\n".join(lines))

    return "\n\n".join(parts) if parts else "No project summary available."


def _get_capabilities_inventory(project_root: Path) -> str:
    """Build a name+description inventory of all discovered skills."""
    try:
        skills = discover_skills(project_root=project_root)
    except Exception:
        return "Could not load skills inventory."
    if not skills:
        return "No skills discovered."
    lines = []
    for name in sorted(skills):
        desc = skills[name].description
        lines.append(f"- **{name}**: {desc}")
    return "\n".join(lines)


def _get_reflection_context(state: StateManager, role: AgentRole) -> str:
    """Load last 5 reflections for this agent."""
    reflections = state.get_reflections(role.value, limit=5)
    if not reflections:
        return "No previous reflections — this is your first cycle."
    lines = []
    for r in reflections:
        lines.append(f"- Cycle {r.cycle_number}: {r.observation} → {r.outcome}")
        if r.lesson:
            lines.append(f"  Lesson: {r.lesson}")
    return "\n".join(lines)
