#!/usr/bin/env python3
"""Render multi-agent consensus JSON into GitHub-friendly review artifacts."""

import hashlib
import html
import json
import os
import sys

ALL_REVIEWERS = ["codex", "claude", "gemini", "grok"]
SEVERITY_ORDER = {"critical": 0, "important": 1, "minor": 2}
SARIF_LEVEL = {"critical": "error", "important": "warning", "minor": "note"}


def strip_fence(text):
    text = text.strip()
    if text.startswith("```"):
        newline = text.find("\n")
        if newline != -1:
            text = text[newline + 1:]
        if text.rstrip().endswith("```"):
            text = text.rstrip()[:-3]
    return text.strip()


def is_consensus(value):
    return isinstance(value, dict) and ("clusters" in value or "unavailable" in value)


def extract_consensus(text):
    text = strip_fence(text)
    if not text:
        return None
    try:
        value = json.loads(text)
        if is_consensus(value):
            return value
    except json.JSONDecodeError:
        pass

    decoder = json.JSONDecoder()
    index = text.find("{")
    while index != -1:
        try:
            value, _ = decoder.raw_decode(text[index:])
            if is_consensus(value):
                return value
        except json.JSONDecodeError:
            pass
        index = text.find("{", index + 1)
    return None


def string_list(value):
    if value is None:
        return []
    if isinstance(value, str):
        return [value] if value.strip() else []
    if isinstance(value, list):
        return [str(item) for item in value if item is not None]
    return [str(value)]


def normalized_path(value):
    path = str(value or "").replace("\\", "/")
    if path[:2] in ("a/", "b/"):
        path = path[2:]
    return path


def start_line(value):
    try:
        return max(1, int(value))
    except (TypeError, ValueError):
        return 1


def finding_text(cluster):
    sources = ", ".join(string_list(cluster.get("sources")))
    title = str(cluster.get("title") or "").strip()
    description = str(cluster.get("description") or "").strip()
    fixes = [fix for fix in string_list(cluster.get("suggested_fixes")) if fix]

    parts = []
    if sources:
        parts.append(f"[{sources}]")
    if title:
        parts.append(title)
    if description:
        parts.append(description)
    if fixes:
        parts.append("Suggested fix: " + " | ".join(fixes))
    return " — ".join(parts) or "Multi-agent review finding"


def to_sarif(clusters):
    rules = []
    for severity in ("critical", "important", "minor"):
        rules.append({
            "id": f"MAR-{severity}",
            "name": f"MultiAgentReview{severity.title()}",
            "shortDescription": {"text": f"{severity.title()} multi-agent review finding"},
            "defaultConfiguration": {"level": SARIF_LEVEL[severity]},
        })

    results = []
    for cluster in clusters:
        path = normalized_path(cluster.get("file"))
        if not path:
            continue
        severity = cluster.get("severity", "minor")
        line = start_line(cluster.get("line"))
        fingerprint_source = (
            f"{path}|{line}|{cluster.get('title', '')}|{cluster.get('description', '')}"
        )
        results.append({
            "ruleId": f"MAR-{severity}",
            "level": SARIF_LEVEL[severity],
            "message": {"text": finding_text(cluster)},
            "locations": [{
                "physicalLocation": {
                    "artifactLocation": {"uri": path},
                    "region": {"startLine": line},
                }
            }],
            "partialFingerprints": {
                "primaryLocationLineHash": hashlib.sha256(
                    fingerprint_source.encode("utf-8")
                ).hexdigest()
            },
        })

    return {
        "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
        "version": "2.1.0",
        "runs": [{
            "tool": {
                "driver": {
                    "name": "Knodr Multi-Agent Review",
                    "informationUri": "https://knodr.com/docs/tutorials/multi-agent-review-ci/",
                    "rules": rules,
                }
            },
            "results": results,
        }],
    }


def to_markdown(clusters, unavailable):
    statuses = [
        f"`{reviewer}` {'unavailable' if reviewer in unavailable else 'ok'}"
        for reviewer in ALL_REVIEWERS
    ]
    lines = ["# Multi-Agent Review", "", "**Reviewers:** " + ", ".join(statuses), ""]

    for severity, heading in (
        ("critical", "Critical"),
        ("important", "Should fix"),
        ("minor", "Advisory"),
    ):
        findings = [item for item in clusters if item.get("severity") == severity]
        lines.extend([f"## {heading} ({len(findings)})", ""])
        if not findings:
            lines.extend(["_None._", ""])
            continue
        for cluster in findings:
            location = f"{normalized_path(cluster.get('file'))}:{start_line(cluster.get('line'))}"
            sources = ", ".join(string_list(cluster.get("sources")))
            lines.append(
                f"- **[{sources}]** `{location}` — "
                f"{str(cluster.get('title') or '').strip()}"
            )
            description = str(cluster.get("description") or "").strip()
            if description:
                lines.append(f"  - {description}")
            for fix in string_list(cluster.get("suggested_fixes")):
                if fix:
                    lines.append(f"  - _fix:_ {fix}")
        lines.append("")
    return "\n".join(lines).rstrip() + "\n"


def to_html(clusters, unavailable):
    escape = html.escape
    markdown = to_markdown(clusters, unavailable)
    rows = []
    for cluster in clusters:
        rows.append(
            "<tr>"
            f"<td>{escape(str(cluster.get('severity') or 'minor'))}</td>"
            f"<td>{escape(normalized_path(cluster.get('file')))}:"
            f"{start_line(cluster.get('line'))}</td>"
            f"<td>{escape(', '.join(string_list(cluster.get('sources'))))}</td>"
            f"<td>{escape(str(cluster.get('title') or ''))}</td>"
            f"<td>{escape(str(cluster.get('description') or ''))}</td>"
            "</tr>"
        )
    css = (
        "body{font:14px system-ui;max-width:1100px;margin:2rem auto;padding:0 1rem;"
        "color:#1f2328}table{width:100%;border-collapse:collapse}th,td{padding:.65rem;"
        "border:1px solid #d0d7de;text-align:left;vertical-align:top}th{background:#f6f8fa}"
        "pre{white-space:pre-wrap;background:#f6f8fa;padding:1rem;border-radius:6px}"
    )
    return (
        "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\">"
        "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
        f"<title>Multi-Agent Review</title><style>{css}</style></head><body>"
        "<h1>Multi-Agent Review</h1><table><thead><tr><th>Severity</th>"
        "<th>Location</th><th>Sources</th><th>Finding</th><th>Description</th>"
        f"</tr></thead><tbody>{''.join(rows)}</tbody></table>"
        f"<h2>Markdown report</h2><pre>{escape(markdown)}</pre></body></html>\n"
    )


def main():
    repository = sys.argv[1] if len(sys.argv) > 1 else "."
    raw = sys.stdin.buffer.read().decode("utf-8", errors="replace").lstrip("\ufeff").strip()
    parsed = extract_consensus(raw)
    data = parsed if parsed is not None else {
        "clusters": [],
        "unavailable": list(ALL_REVIEWERS),
    }

    clusters = [
        cluster for cluster in (data.get("clusters") or [])
        if isinstance(cluster, dict)
    ]
    unavailable = set(string_list(data.get("unavailable")))
    for cluster in clusters:
        if cluster.get("severity") not in SEVERITY_ORDER:
            cluster["severity"] = "minor"
    clusters.sort(key=lambda item: SEVERITY_ORDER[item["severity"]])

    output_dir = os.path.join(repository, ".mar-out")
    os.makedirs(output_dir, exist_ok=True)
    with open(os.path.join(output_dir, "mar-results.sarif"), "w", encoding="utf-8") as file:
        json.dump(to_sarif(clusters), file, indent=2, ensure_ascii=False)
    with open(os.path.join(output_dir, "mar-report.md"), "w", encoding="utf-8") as file:
        file.write(to_markdown(clusters, unavailable))
    with open(os.path.join(output_dir, "mar-report.html"), "w", encoding="utf-8") as file:
        file.write(to_html(clusters, unavailable))

    if parsed is None:
        sys.stderr.write("render: consensus output was empty or unparseable\n")
        return 1
    if not [reviewer for reviewer in ALL_REVIEWERS if reviewer not in unavailable]:
        sys.stderr.write("render: all reviewers were unavailable\n")
        return 1

    print(f"rendered {len(clusters)} clusters; {len(unavailable)} reviewers unavailable")
    return 0


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