#!/usr/bin/env python3
"""Review discovered skill-source candidates into a bounded triage report.

This script consumes the latest `reports/discovery/*.json` generated by
`scripts/discover_skill_sources.py` and produces review artifacts. It does not
copy third-party content, clone repositories, execute candidate code, or create
skills directly. Its purpose is to turn raw discovery into a queue of concrete
human/agent review decisions.
"""

from __future__ import annotations

import datetime as dt
import json
import re
from pathlib import Path
from typing import Any

ROOT = Path(__file__).resolve().parents[1]
DISCOVERY = ROOT / "reports" / "discovery"
REVIEW = ROOT / "reports" / "review"
SOURCES = ROOT / "sources" / "candidates"

ALLOW_TOPICS = {
    "agent",
    "agents",
    "ai-agent",
    "llm",
    "mcp",
    "prompt-engineering",
    "workflow",
    "automation",
    "cursor",
    "claude",
    "codex",
    "skills",
    "memory",
}

RISK_TERMS = {
    "browser",
    "automation",
    "shell",
    "terminal",
    "execute",
    "mcp",
    "server",
    "credential",
    "token",
    "oauth",
    "api key",
    "docker",
    "agent",
}

SKIP_PATTERNS = [
    re.compile(r"\bawesome\b", re.I),
    re.compile(r"\bcollection\b", re.I),
    re.compile(r"\blist\b", re.I),
    re.compile(r"\bcurated\b", re.I),
]


def latest_discovery_json() -> Path | None:
    candidates = sorted(DISCOVERY.glob("*.json"))
    return candidates[-1] if candidates else None


def safe_int(value: Any) -> int:
    return value if isinstance(value, int) else 0


def text_blob(item: dict[str, Any]) -> str:
    parts = [
        str(item.get("name") or ""),
        str(item.get("title") or ""),
        str(item.get("description") or ""),
        str(item.get("path") or ""),
        str(item.get("skill_slug") or ""),
        " ".join(str(topic) for topic in item.get("topics", []) if isinstance(topic, str)),
        str(item.get("query") or ""),
        str(item.get("watch_status") or ""),
    ]
    return " ".join(parts).lower()


def candidate_id(item: dict[str, Any]) -> str:
    raw = item.get("name") or item.get("title") or item.get("url") or "candidate"
    return re.sub(r"[^a-zA-Z0-9._-]+", "-", str(raw)).strip("-").lower()[:96] or "candidate"


def score_item(item: dict[str, Any]) -> tuple[int, list[str], str]:
    if item.get("error"):
        return 0, ["discovery_error"], "skip"

    blob = text_blob(item)
    topics = {str(topic).lower() for topic in item.get("topics", []) if isinstance(topic, str)}
    stars = safe_int(item.get("stars"))
    license_id = item.get("license")
    source = item.get("source") or "unknown"

    score = 0
    reasons: list[str] = []

    if source == "github_search":
        score += 2
        reasons.append("repo_source")
    elif source == "hacker_news_algolia":
        score += 1
        reasons.append("discussion_source")
    elif source == "github_watchlist":
        score += 5
        reasons.append("watched_upstream_source")
    elif source == "registry_watchlist":
        score += 3
        reasons.append("watched_registry_source")

    if item.get("watch_status") == "needs_delta_review":
        score += 3
        reasons.append("watchlist_delta_needs_review")
    elif item.get("watch_status") == "known_reviewed_or_distilled":
        score -= 1
        reasons.append("known_source_watch_only")

    if topics & ALLOW_TOPICS:
        score += 3
        reasons.append("topic_match")
    if any(term in blob for term in ["skill", "agent", "mcp", "cursor", "claude", "codex", "workflow", "memory", "prompt"]):
        score += 3
        reasons.append("semantic_match")
    if stars >= 1000:
        score += 3
        reasons.append("high_adoption")
    elif stars >= 100:
        score += 2
        reasons.append("moderate_adoption")
    elif stars >= 25:
        score += 1
        reasons.append("some_adoption")
    if license_id and license_id not in {"NOASSERTION", "Other"}:
        score += 2
        reasons.append("license_declared")
    else:
        reasons.append("license_unclear")
    if any(pattern.search(blob) for pattern in SKIP_PATTERNS):
        score -= 2
        reasons.append("likely_list_or_awesome_repo")

    if score >= 8:
        decision = "review_next"
    elif score >= 5:
        decision = "watch"
    else:
        decision = "skip_or_low_priority"
    return score, reasons, decision


def risk_level(item: dict[str, Any]) -> str:
    blob = text_blob(item)
    hits = [term for term in RISK_TERMS if term in blob]
    if any(term in hits for term in ["credential", "token", "oauth", "api key", "shell", "terminal", "execute"]):
        return "high"
    if any(term in hits for term in ["browser", "automation", "mcp", "server", "docker", "agent"]):
        return "medium"
    return "low"


def dedupe(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
    seen: set[str] = set()
    out: list[dict[str, Any]] = []
    for item in items:
        key = str(item.get("url") or item.get("name") or item.get("title") or json.dumps(item, sort_keys=True))
        if key in seen:
            continue
        seen.add(key)
        out.append(item)
    return out


def review_items(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
    reviewed: list[dict[str, Any]] = []
    for item in dedupe(items):
        score, reasons, decision = score_item(item)
        reviewed.append(
            {
                "id": candidate_id(item),
                "name": item.get("name") or item.get("title"),
                "url": item.get("url"),
                "source": item.get("source"),
                "source_family": item.get("source_family"),
                "query": item.get("query"),
                "description": item.get("description"),
                "repo": item.get("repo"),
                "path": item.get("path"),
                "skill_slug": item.get("skill_slug"),
                "watch_status": item.get("watch_status"),
                "stars": item.get("stars"),
                "license": item.get("license"),
                "pushed_at": item.get("pushed_at"),
                "score": score,
                "reasons": reasons,
                "decision": decision,
                "risk_level": risk_level(item),
                "review_boundary": item.get("review_boundary") or "metadata_triage_only_no_content_copy_no_code_execution",
            }
        )
    return sorted(reviewed, key=lambda row: (-int(row["score"]), str(row.get("name") or "")))


def write_outputs(source_path: Path, reviewed: list[dict[str, Any]]) -> None:
    today = dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%d")
    REVIEW.mkdir(parents=True, exist_ok=True)
    SOURCES.mkdir(parents=True, exist_ok=True)

    summary = {
        "generated_at_utc": dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z"),
        "source_report": str(source_path.relative_to(ROOT)),
        "candidate_count": len(reviewed),
        "review_next_count": sum(1 for item in reviewed if item["decision"] == "review_next"),
        "watch_count": sum(1 for item in reviewed if item["decision"] == "watch"),
        "skip_or_low_priority_count": sum(1 for item in reviewed if item["decision"] == "skip_or_low_priority"),
        "high_risk_count": sum(1 for item in reviewed if item["risk_level"] == "high"),
        "medium_risk_count": sum(1 for item in reviewed if item["risk_level"] == "medium"),
        "items": reviewed,
    }

    json_path = REVIEW / f"{today}.json"
    md_path = REVIEW / f"{today}.md"
    candidates_path = SOURCES / f"{today}.json"
    json_path.write_text(json.dumps(summary, indent=2, sort_keys=True) + "\n", encoding="utf-8")
    candidates_path.write_text(json.dumps(reviewed, indent=2, sort_keys=True) + "\n", encoding="utf-8")

    lines = [
        "---",
        "type: Candidate Review Report",
        f"title: Skill Candidate Review {today}",
        "description: Metadata-only review of discovered skill-source candidates.",
        "tags: [skills, discovery, review, candidates, risk]",
        "okf_version: \"0.2\"",
        "status: active",
        "---",
        "",
        f"# Skill Candidate Review {today}",
        "",
        "## Boundary",
        "",
        "This is metadata-only triage. No third-party content was copied, no candidate code was cloned or executed, and no skill was normalized automatically.",
        "",
        "## Summary",
        "",
        f"- Source report: `{source_path.relative_to(ROOT)}`",
        f"- Candidates reviewed: {len(reviewed)}",
        f"- Review next: {summary['review_next_count']}",
        f"- Watch: {summary['watch_count']}",
        f"- Skip or low priority: {summary['skip_or_low_priority_count']}",
        f"- High risk: {summary['high_risk_count']}",
        f"- Medium risk: {summary['medium_risk_count']}",
        "",
        "## Review-next candidates",
        "",
    ]
    for item in [row for row in reviewed if row["decision"] == "review_next"][:25]:
        path_note = f"; path={item.get('path')}" if item.get("path") else ""
        lines.append(f"- [{item.get('name')}]({item.get('url')}) — score={item['score']}; risk={item['risk_level']}; license={item.get('license')}; reasons={', '.join(item['reasons'])}{path_note}")
    lines += ["", "## Watch candidates", ""]
    for item in [row for row in reviewed if row["decision"] == "watch"][:25]:
        path_note = f"; path={item.get('path')}" if item.get("path") else ""
        lines.append(f"- [{item.get('name')}]({item.get('url')}) — score={item['score']}; risk={item['risk_level']}; license={item.get('license')}; reasons={', '.join(item['reasons'])}{path_note}")
    lines += ["", "## Next action", "", "Source Reviewer should inspect only `review_next` candidates first, verify license and security/runtime surfaces, then create either source profiles, skip notes, or normalization queue items. Do not copy third-party content directly into skills.", ""]
    md_path.write_text("\n".join(lines), encoding="utf-8")


def main() -> int:
    source_path = latest_discovery_json()
    if source_path is None:
        print(json.dumps({"error": "no discovery json report found", "discovery_dir": str(DISCOVERY)}, indent=2))
        return 0
    payload = json.loads(source_path.read_text(encoding="utf-8"))
    items: list[dict[str, Any]] = []
    if isinstance(payload.get("watched_repositories"), list):
        items.extend(payload["watched_repositories"])
    if isinstance(payload.get("registries"), list):
        items.extend(payload["registries"])
    if isinstance(payload.get("github"), list):
        items.extend(payload["github"])
    if isinstance(payload.get("hacker_news"), list):
        items.extend(payload["hacker_news"])
    reviewed = review_items(items)
    write_outputs(source_path, reviewed)
    print(json.dumps({"source": str(source_path.relative_to(ROOT)), "reviewed": len(reviewed), "review_next": sum(1 for item in reviewed if item["decision"] == "review_next")}, indent=2))
    return 0


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