#!/usr/bin/env python3
from __future__ import annotations

import argparse
import difflib
import json
import re
import subprocess
import sys
from typing import Any


ID_PATTERN = re.compile(r"^[a-z0-9][a-z0-9_-]*$", re.IGNORECASE)


def normalize_text(value: str) -> str:
    return re.sub(r"[\s_\-]+", "", value.strip().lower())


def run_sophhub_agent_list() -> tuple[int, str, str]:
    result = subprocess.run(
        ["npx", "-y", "sophhub@latest", "agent", "list", "--json"],
        capture_output=True,
        text=True,
        check=False,
    )
    return result.returncode, result.stdout, result.stderr


def extract_agents_from_json(data: Any) -> list[dict[str, str]]:
    if isinstance(data, dict):
        for key in ("agents", "items", "data", "list"):
            value = data.get(key)
            if isinstance(value, list):
                return extract_agents_from_json(value)
        if any(key in data for key in ("id", "agent_id", "name", "description", "desc")):
            return [normalize_agent_record(data)]
        return []

    if isinstance(data, list):
        result: list[dict[str, str]] = []
        for item in data:
            if isinstance(item, dict):
                result.append(normalize_agent_record(item))
            elif isinstance(item, str):
                parsed = parse_text_line(item)
                if parsed:
                    result.append(parsed)
        return dedupe_agents(result)

    return []


def normalize_agent_record(item: dict[str, Any]) -> dict[str, str]:
    agent_id = first_non_empty_str(
        item.get("id"),
        item.get("agent_id"),
        item.get("agentId"),
        item.get("slug"),
        item.get("key"),
        item.get("code"),
    )
    name = first_non_empty_str(item.get("name"), item.get("title"), item.get("displayName"))
    description = first_non_empty_str(
        item.get("description"),
        item.get("desc"),
        item.get("summary"),
        item.get("intro"),
    )

    if not agent_id and name and ID_PATTERN.match(name):
        agent_id = name

    return {
        "agent_id": agent_id or "",
        "name": name or "",
        "description": description or "",
    }


def first_non_empty_str(*values: Any) -> str | None:
    for value in values:
        if isinstance(value, str) and value.strip():
            return value.strip()
    return None


def parse_text_line(line: str) -> dict[str, str] | None:
    raw = line.strip()
    if not raw:
        return None
    if raw.startswith(("npm ", "Error:", "error:", "WARN", "warning:")):
        return None
    if set(raw) <= {"-", "=", "|", " "}:
        return None

    cleaned = re.sub(r"^\s*(?:[-*]|\d+\.)\s*", "", raw)
    if "|" in cleaned:
        parts = [part.strip() for part in cleaned.split("|") if part.strip()]
        if len(parts) >= 2 and ID_PATTERN.match(parts[0]):
            return {
                "agent_id": parts[0],
                "name": parts[1] if len(parts) >= 2 else "",
                "description": " | ".join(parts[2:]) if len(parts) >= 3 else "",
            }

    for splitter in (r"\s+-\s+", r"\s{2,}", r"\s+:\s+"):
        parts = re.split(splitter, cleaned, maxsplit=2)
        if len(parts) >= 2 and ID_PATTERN.match(parts[0].strip()):
            return {
                "agent_id": parts[0].strip(),
                "name": parts[1].strip() if len(parts) >= 2 else "",
                "description": parts[2].strip() if len(parts) >= 3 else "",
            }

    first_word, _, rest = cleaned.partition(" ")
    if ID_PATTERN.match(first_word):
        return {
            "agent_id": first_word.strip(),
            "name": "",
            "description": rest.strip(),
        }

    return None


def parse_agents_output(stdout: str) -> list[dict[str, str]]:
    stripped = stdout.strip()
    if not stripped:
        return []

    try:
        data = json.loads(stripped)
    except json.JSONDecodeError:
        data = None

    if data is not None:
        return dedupe_agents(extract_agents_from_json(data))

    result: list[dict[str, str]] = []
    for line in stripped.splitlines():
        parsed = parse_text_line(line)
        if parsed:
            result.append(parsed)
    return dedupe_agents(result)


def dedupe_agents(agents: list[dict[str, str]]) -> list[dict[str, str]]:
    deduped: list[dict[str, str]] = []
    seen: set[tuple[str, str]] = set()
    for agent in agents:
        agent_id = agent.get("agent_id", "").strip()
        name = agent.get("name", "").strip()
        description = agent.get("description", "").strip()
        key = (agent_id.lower(), name.lower())
        if not agent_id or key in seen:
            continue
        deduped.append(
            {
                "agent_id": agent_id,
                "name": name,
                "description": description,
            }
        )
        seen.add(key)
    return deduped


def score_agent(agent: dict[str, str], query: str) -> tuple[int, str]:
    normalized_query = normalize_text(query)
    agent_id = agent.get("agent_id", "")
    name = agent.get("name", "")
    description = agent.get("description", "")

    fields = [
        ("agent_id", agent_id),
        ("name", name),
        ("description", description),
    ]

    for label, value in fields:
        if normalize_text(value) == normalized_query and value:
            return 100, f"exact_{label}"

    if normalized_query in normalize_text(agent_id):
        return 95, "contains_agent_id"
    if normalized_query in normalize_text(name):
        return 90, "contains_name"
    if normalized_query in normalize_text(description):
        return 80, "contains_description"

    ratios = []
    for label, value in fields[:2]:
        if value:
            ratio = difflib.SequenceMatcher(None, normalized_query, normalize_text(value)).ratio()
            ratios.append((int(ratio * 100), f"similar_{label}"))

    if ratios:
        best_score, best_reason = max(ratios, key=lambda item: item[0])
        if best_score >= 45:
            return best_score, best_reason

    return 0, "no_match"


def build_resolution(agents: list[dict[str, str]], query: str, limit: int) -> dict[str, Any]:
    scored = []
    for agent in agents:
        score, reason = score_agent(agent, query)
        if score > 0:
            item = dict(agent)
            item["score"] = score
            item["match_reason"] = reason
            scored.append(item)

    scored.sort(key=lambda item: (-item["score"], item["agent_id"]))
    candidates = scored[:limit]

    exact_agent = next((item for item in candidates if item["score"] == 100 and item["match_reason"] == "exact_agent_id"), None)
    resolved_agent_id = exact_agent["agent_id"] if exact_agent else None
    if resolved_agent_id:
        confirmation_message = f"已精确匹配到 Agent：{resolved_agent_id}，可以继续安装。"
        suggested_replies = [resolved_agent_id]
        status = "EXACT_MATCH"
    elif candidates:
        lines = ["我找到了以下候选 Agent，请确认要安装哪一个："]
        for index, candidate in enumerate(candidates, start=1):
            name = f"（{candidate['name']}）" if candidate.get("name") else ""
            desc = f" - {candidate['description']}" if candidate.get("description") else ""
            lines.append(f"{index}. {candidate['agent_id']}{name}{desc}")
        confirmation_message = "\n".join(lines)
        suggested_replies = [candidate["agent_id"] for candidate in candidates]
        status = "NEEDS_CONFIRMATION"
    else:
        confirmation_message = "没有找到匹配的 Agent，请换一个更准确的名称、ID 或功能描述。"
        suggested_replies = []
        status = "NO_MATCH"

    return {
        "query": query,
        "status": status,
        "resolved_agent_id": resolved_agent_id,
        "needs_confirmation": resolved_agent_id is None,
        "candidates": candidates,
        "confirmation_message": confirmation_message,
        "suggested_replies": suggested_replies,
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="列出可用 Agent，并支持模糊匹配")
    parser.add_argument("--query", help="用户输入的 Agent 名称、别名或描述关键词")
    parser.add_argument("--limit", type=int, default=5, help="返回候选数量")
    parser.add_argument("--input-file", help="从文件读取 sophhub agent list --json 输出，便于调试")
    args = parser.parse_args()

    if args.input_file:
        stdout = open(args.input_file, "r", encoding="utf-8").read()
        stderr = ""
        returncode = 0
    else:
        returncode, stdout, stderr = run_sophhub_agent_list()

    agents = parse_agents_output(stdout)

    payload: dict[str, Any] = {
        "ok": returncode == 0,
        "total_agents": len(agents),
        "agents": agents,
        "raw_stdout": stdout,
    }

    if stderr:
        payload["stderr"] = stderr

    if args.query:
        payload["resolution"] = build_resolution(agents, args.query, max(1, args.limit))

    json.dump(payload, sys.stdout, indent=2, ensure_ascii=False)
    sys.stdout.write("\n")
    return 0 if args.input_file or returncode == 0 else returncode


if __name__ == "__main__":
    raise SystemExit(main())
