"""Sub-agent loader for discovering and parsing sub-agent definitions from skill directories.

Sub-agents are defined as markdown files with YAML frontmatter in skill directories:
    projects/robot/{bot_id}/skills/{skill_name}/agents/*.md

Each file has the format:
    ---
    name: code-reviewer
    description: Reviews code for quality and security issues.
    tools: rag_retrieve, table_rag_retrieve
    ---

    System prompt for the sub-agent...
"""

import logging
import os
import re
from pathlib import Path
from typing import Optional

import yaml
from deepagents.middleware.subagents import SubAgent
from langchain.tools import BaseTool
from langchain_core.language_models import BaseChatModel

from agent.plugin_hook_loader import _get_skill_dirs

logger = logging.getLogger('app')

# Regex to extract YAML frontmatter and body from markdown files
_FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n?(.*)$", re.DOTALL)


def _parse_agent_md(file_path: Path) -> Optional[dict]:
    """Parse a sub-agent markdown file with YAML frontmatter.

    Args:
        file_path: Path to the .md file.

    Returns:
        Dict with keys: name, description, system_prompt, tool_names (list[str] | None).
        None if parsing fails.
    """
    try:
        content = file_path.read_text(encoding="utf-8")
    except OSError as e:
        logger.warning(f"Failed to read sub-agent file {file_path}: {e}")
        return None

    match = _FRONTMATTER_RE.match(content)
    if not match:
        logger.warning(f"Sub-agent file {file_path} has no valid frontmatter")
        return None

    frontmatter_str, body = match.group(1), match.group(2)

    try:
        frontmatter = yaml.safe_load(frontmatter_str)
    except yaml.YAMLError as e:
        logger.warning(f"Invalid YAML in sub-agent file {file_path}: {e}")
        return None

    if not isinstance(frontmatter, dict):
        logger.warning(f"Frontmatter in {file_path} is not a dict")
        return None

    name = frontmatter.get("name", "").strip() if isinstance(frontmatter.get("name"), str) else ""
    description = frontmatter.get("description", "").strip() if isinstance(frontmatter.get("description"), str) else ""

    if not name:
        logger.warning(f"Sub-agent file {file_path} missing required 'name' field")
        return None
    if not description:
        logger.warning(f"Sub-agent file {file_path} missing required 'description' field")
        return None

    # Parse optional tools field: comma-separated tool names
    tool_names = None
    tools_field = frontmatter.get("tools")
    if tools_field is not None:
        if isinstance(tools_field, str):
            tool_names = [t.strip() for t in tools_field.split(",") if t.strip()]
        elif isinstance(tools_field, list):
            tool_names = [str(t).strip() for t in tools_field if str(t).strip()]
        else:
            logger.warning(f"Invalid 'tools' field in {file_path}, expected string or list")

    return {
        "name": name,
        "description": description,
        "system_prompt": body.strip(),
        "tool_names": tool_names,
        "source": str(file_path),
    }


def _filter_tools_by_names(all_tools: list[BaseTool], tool_names: list[str]) -> list[BaseTool]:
    """Filter MCP tools by name whitelist.

    Args:
        all_tools: All available MCP tools.
        tool_names: Whitelist of tool names to include.

    Returns:
        Filtered list of tools. Logs warning for names not found.
    """
    tool_lookup = {tool.name: tool for tool in all_tools}
    filtered = []
    for name in tool_names:
        if name in tool_lookup:
            filtered.append(tool_lookup[name])
        else:
            available = list(tool_lookup.keys())
            logger.warning(f"Sub-agent tool '{name}' not found in MCP tools. Available: {available}")
    return filtered


async def load_subagents(
    bot_id: str,
    tools: list[BaseTool],
    model: BaseChatModel,
) -> list[SubAgent]:
    """Load sub-agent definitions from skill directories.

    Scans all skill directories for the given bot_id, looking for agents/*.md files
    in each skill subdirectory.

    Args:
        bot_id: Bot identifier for locating skill directories.
        tools: All available MCP tools for filtering.
        model: The main agent's model, used by each sub-agent.

    Returns:
        List of SubAgent dicts. Empty list if no sub-agents found.
    """
    skill_dirs = _get_skill_dirs(bot_id)
    parsed_agents: dict[str, dict] = {}  # name -> parsed dict (last-wins for dedup)

    for skill_dir in skill_dirs:
        if not os.path.exists(skill_dir):
            continue

        for skill_name in os.listdir(skill_dir):
            skill_path = os.path.join(skill_dir, skill_name)
            if not os.path.isdir(skill_path):
                continue

            agents_dir = Path(skill_path) / "agents"
            if not agents_dir.exists():
                continue

            for md_file in agents_dir.glob("*.md"):
                parsed = _parse_agent_md(md_file)
                if parsed is None:
                    continue

                name = parsed["name"]
                if name in parsed_agents:
                    logger.warning(
                        f"Duplicate sub-agent name '{name}': "
                        f"{parsed_agents[name]['source']} overridden by {parsed['source']}"
                    )
                parsed_agents[name] = parsed

    if not parsed_agents:
        return []

    # Build SubAgent dicts with model and filtered tools
    subagents: list[SubAgent] = []
    for name, parsed in parsed_agents.items():
        # Filter tools: if tool_names specified, filter; otherwise inherit all
        if parsed["tool_names"] is not None:
            filtered_tools = _filter_tools_by_names(tools, parsed["tool_names"])
        else:
            filtered_tools = list(tools)

        subagent: SubAgent = {
            "name": name,
            "description": parsed["description"],
            "system_prompt": parsed["system_prompt"],
            "model": model,
            "tools": filtered_tools,
        }
        subagents.append(subagent)
        logger.info(f"Loaded sub-agent '{name}' with {len(filtered_tools)} tools from {parsed['source']}")

    return subagents
