#!/usr/bin/env python3
"""AI code review for Gitea pull requests, powered by Claude Code.

Reads the PR diff (base branch vs HEAD), asks Claude Code (headless `claude -p`)
to review it, posts the findings as a PR comment via the Gitea API, and exits
non-zero only when the review contains at least one severe finding — so the
job can gate merges without blocking on nitpicks.

Uses only the Python standard library; the review itself runs through the
Claude Code CLI, authenticated with a subscription OAuth token.

Required environment:
    CLAUDE_CODE_OAUTH_TOKEN  token from `claude setup-token` (subscription auth)
    GITEA_TOKEN              Gitea access token for posting PR comments
    GITEA_SERVER_URL         e.g. https://privaterepo.sitaru.org
    GITEA_REPOSITORY         owner/repo
    PR_NUMBER                pull request index
    BASE_REF                 base branch name (e.g. main)
"""

import json
import os
import subprocess
import sys
import urllib.request

MAX_DIFF_CHARS = 150_000

PROMPT = """You are reviewing a pull request for SchoolCompare, a UK school
comparison site (FastAPI backend, Next.js frontend, Airflow/dbt data pipeline,
deployed via Gitea Actions to a staging-then-production Docker setup).

The PR diff is provided on stdin.

Report correctness bugs, security issues, data-loss risks, and broken deploy/CI
configuration. Mark a finding "severe" only if it would break production, leak
data, or corrupt data — severe findings block the merge. Everything else
(style, performance suggestions, minor cleanups) is "minor". Do not invent
findings: an empty findings list is a perfectly good review of a clean diff.

Respond with ONLY a JSON object (no markdown fences, no prose) of this shape:
{
  "summary": "two or three sentences on what the change does and its health",
  "findings": [
    {"severity": "severe" | "minor", "file": "path", "issue": "description"}
  ]
}"""


def get_diff(base_ref: str) -> str:
    subprocess.run(
        ["git", "fetch", "origin", base_ref],
        check=True,
        capture_output=True,
    )
    diff = subprocess.run(
        ["git", "diff", f"origin/{base_ref}...HEAD"],
        check=True,
        capture_output=True,
        text=True,
    ).stdout
    if len(diff) > MAX_DIFF_CHARS:
        diff = diff[:MAX_DIFF_CHARS] + "\n\n[diff truncated for review]"
    return diff


def review(diff: str) -> dict:
    proc = subprocess.run(
        ["claude", "-p", PROMPT, "--output-format", "json"],
        input=diff,
        capture_output=True,
        text=True,
        timeout=900,
    )
    if proc.returncode != 0:
        raise RuntimeError(f"claude CLI failed:\n{proc.stderr}")
    envelope = json.loads(proc.stdout)
    result = envelope["result"].strip()
    # Defensive: strip markdown fences if the model added them anyway
    if result.startswith("```"):
        result = result.split("\n", 1)[1].rsplit("```", 1)[0]
    return json.loads(result)


def format_comment(result: dict) -> str:
    lines = ["## 🤖 AI Code Review (Claude Code)", "", result["summary"], ""]
    severe = [f for f in result["findings"] if f["severity"] == "severe"]
    minor = [f for f in result["findings"] if f["severity"] == "minor"]
    if severe:
        lines.append("### 🔴 Severe (blocks merge)")
        lines += [f"- **{f['file']}**: {f['issue']}" for f in severe]
        lines.append("")
    if minor:
        lines.append("### 🟡 Minor")
        lines += [f"- **{f['file']}**: {f['issue']}" for f in minor]
        lines.append("")
    if not result["findings"]:
        lines.append("✅ No issues found.")
    return "\n".join(lines)


def post_comment(body: str) -> None:
    server = os.environ["GITEA_SERVER_URL"].rstrip("/")
    repo = os.environ["GITEA_REPOSITORY"]
    pr = os.environ["PR_NUMBER"]
    req = urllib.request.Request(
        f"{server}/api/v1/repos/{repo}/issues/{pr}/comments",
        data=json.dumps({"body": body}).encode(),
        headers={
            "Authorization": f"token {os.environ['GITEA_TOKEN']}",
            "Content-Type": "application/json",
        },
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=30) as resp:
        if resp.status >= 300:
            raise RuntimeError(f"Comment post failed: HTTP {resp.status}")


def main() -> int:
    diff = get_diff(os.environ["BASE_REF"])
    if not diff.strip():
        print("Empty diff, nothing to review")
        return 0
    result = review(diff)
    comment = format_comment(result)
    print(comment)
    post_comment(comment)
    severe = [f for f in result["findings"] if f["severity"] == "severe"]
    if severe:
        print(f"\n{len(severe)} severe finding(s) — failing the check", file=sys.stderr)
        return 1
    return 0


if __name__ == "__main__":
    sys.exit(main())
