"""Plugin contoh: konsentrasi kontribusi per file, lewat API HTTP socio-graph.

Untuk tiap file di satu repo: berapa orang yang pernah mengubahnya, dan berapa porsi commit milik
kontributor teratas. File yang hampir seluruhnya diubah satu orang adalah kandidat *knowledge gap*
(pengetahuan terkonsentrasi). Algoritmanya berjalan di mesin pengguna; server hanya menjawab query
baca, jadi skrip ini bisa disalin dan diubah bebas.

    python concentration.py --app http://127.0.0.1:8080 --repo pallets/click

Hanya butuh httpx (`pip install httpx`).
"""

import argparse
from collections import defaultdict

import httpx

CONTRIBUTIONS_QUERY = """
MATCH (p:Person)-[:AUTHORED]->(c:Commit)-[:MODIFIES]->(f:File {repo: $repo})
RETURN f.path AS file, p.login AS person, count(DISTINCT c) AS commits
"""


class SocioGraphClient:
    """Klien kecil untuk API plugin (docs/plugin-api.md §1)."""

    def __init__(self, base_url: str, workspace: str = "public", http: httpx.Client | None = None):
        self._http = http or httpx.Client(base_url=base_url, timeout=120)
        self._prefix = f"/api/w/{workspace}"

    def _check(self, response: httpx.Response) -> dict:
        if response.status_code == 400:
            body = response.json()
            raise RuntimeError(f"query ditolak ({body.get('code')}): {body.get('message')}")
        response.raise_for_status()
        return response.json()

    def schema(self) -> dict:
        return self._check(self._http.get(f"{self._prefix}/schema"))

    def query(self, cypher: str, *, repo: str | None = None, scope: str = "repo", params: dict | None = None) -> dict:
        body = {"cypher": cypher, "repo": repo, "scope": scope, "params": params or {}}
        return self._check(self._http.post(f"{self._prefix}/query", json=body))


def concentration(rows: list[list]) -> list[dict]:
    """rows = [file, person, commits] -> statistik per file, paling terkonsentrasi dulu."""
    per_file: dict[str, dict[str, int]] = defaultdict(dict)
    for file, person, commits in rows:
        per_file[file][person] = commits
    stats = []
    for file, people in per_file.items():
        total = sum(people.values())
        # Commit terbanyak; kalau seri, nama urut abjad pertama (supaya hasilnya stabil).
        top_person, top_commits = min(people.items(), key=lambda kv: (-kv[1], kv[0]))
        stats.append({
            "file": file,
            "contributors": len(people),
            "commits": total,
            "top_person": top_person,
            "top_share": round(top_commits / total, 3),
        })
    return sorted(stats, key=lambda s: (-s["top_share"], -s["commits"], s["file"]))


def at_risk(stats: list[dict], threshold: float = 0.8, min_commits: int = 5) -> list[dict]:
    """File yang cukup sering diubah tapi didominasi satu orang."""
    return [s for s in stats if s["top_share"] >= threshold and s["commits"] >= min_commits]


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    parser.add_argument("--app", default="http://127.0.0.1:8080", help="alamat aplikasi socio-graph")
    parser.add_argument("--workspace", default="public")
    parser.add_argument("--repo", required=True, help="owner/name")
    parser.add_argument("--threshold", type=float, default=0.8, help="porsi kontributor teratas (0–1)")
    parser.add_argument("--min-commits", type=int, default=5)
    parser.add_argument("--top", type=int, default=20)
    args = parser.parse_args()

    client = SocioGraphClient(args.app, args.workspace)
    result = client.query(CONTRIBUTIONS_QUERY, repo=args.repo)
    if result["truncated"]:
        print("Peringatan: hasil terpotong di batas baris server; statistik bisa tidak lengkap.")
    stats = concentration(result["rows"])
    risky = at_risk(stats, args.threshold, args.min_commits)
    print(f"{args.repo}: {len(stats)} file, {len(risky)} file dengan kontributor teratas ≥ "
          f"{args.threshold:.0%} dari ≥ {args.min_commits} commit\n")
    print(f"{'porsi':>6} {'commit':>6} {'orang':>5}  {'kontributor teratas':<24} file")
    for s in risky[: args.top]:
        print(f"{s['top_share']:>6.0%} {s['commits']:>6} {s['contributors']:>5}  {s['top_person']:<24} {s['file']}")


if __name__ == "__main__":
    main()
