#!/usr/bin/env python3
"""Regenerate code-reviews/README.md from the per-module findings.md files.

The findings files are the source of truth; README.md is a generated index.
Run this after resolving or re-triaging a finding so the aggregated tables stay
in sync (see REVIEW-PROCESS.md section 5).

Usage:
    python3 regen-readme.py            # rewrite README.md
    python3 regen-readme.py --check    # exit 1 if README.md is stale (for CI)

Works from any directory — paths are resolved relative to this script.
"""

import os
import re
import sys

BASE = os.path.dirname(os.path.abspath(__file__))
SEVERITY_ORDER = {"Critical": 0, "High": 1, "Medium": 2, "Low": 3}
PENDING_STATUSES = {"Open", "In Progress"}


def discover_modules():
    """Module folders are every subdirectory of code-reviews/ holding a findings.md,
    excluding the _template folder. Returned sorted for a stable README order."""
    modules = []
    for name in sorted(os.listdir(BASE)):
        if name.startswith(("_", ".")):
            continue
        if os.path.isfile(os.path.join(BASE, name, "findings.md")):
            modules.append(name)
    return modules


def parse_findings(module):
    """Parse one module's findings.md into (module, id, severity, title, status) tuples."""
    text = open(os.path.join(BASE, module, "findings.md")).read()
    findings = []
    for block in re.split(r"^### ", text, flags=re.M)[1:]:
        head = block.splitlines()[0].strip()
        m = re.match(r"([A-Za-z][A-Za-z0-9]*-\d+)\b(.*)", head)
        if not m:
            raise SystemExit(f"{module}/findings.md: unparseable finding heading: {head!r}")
        fid = m.group(1).strip()
        title = m.group(2).strip().lstrip("—–-").strip().replace("|", "\\|")
        sev = re.search(r"\|\s*Severity\s*\|\s*([A-Za-z]+)", block)
        status = re.search(r"\|\s*Status\s*\|\s*([A-Za-z' ]+?)\s*\|", block)
        if not sev or not status:
            raise SystemExit(f"{module}/findings.md: {fid} is missing a Severity or Status field")
        findings.append((module, fid, sev.group(1), title, status.group(1).strip()))
    return findings


def finding_number(finding):
    return int(re.search(r"-(\d+)$", finding[1]).group(1))


def build_readme(modules, per_module):
    pending = sorted(
        (f for fs in per_module.values() for f in fs if f[4] in PENDING_STATUSES),
        key=lambda f: (SEVERITY_ORDER.get(f[2], 9), f[0], finding_number(f)),
    )

    def severity_total(sev):
        return sum(1 for f in pending if f[2] == sev)

    def open_count(module, sev):
        return sum(1 for f in per_module[module]
                   if f[2] == sev and f[4] in PENDING_STATUSES)

    lines = []
    add = lines.append

    add("# Code Reviews")
    add("")
    add("Comprehensive, per-module code reviews of the ScadaLink codebase. Each module (one")
    add("buildable project under `src/`) has its own folder containing a `findings.md`. This")
    add("README is the aggregated index — the single place to see all outstanding work.")
    add("")
    add("> Generated by `regen-readme.py` from the per-module `findings.md` files. Do not")
    add("> edit by hand — edit the findings files and re-run the script.")
    add("")
    add("## How it works")
    add("")
    add("- Reviews are performed one module at a time against a fixed checklist.")
    add("- Every finding is recorded in the module's `findings.md` with a severity and status.")
    add("- Findings are **never deleted** — they are closed by changing their status, keeping")
    add("  a full audit trail.")
    add("- This README aggregates every **pending** finding (`Open` / `In Progress`) across all")
    add("  modules.")
    add("")
    add("See **[REVIEW-PROCESS.md](REVIEW-PROCESS.md)** for the full procedure: the review")
    add("checklist, severity definitions, finding format, and how to mark items resolved.")
    add("")
    add("## Layout")
    add("")
    add("```")
    add("code-reviews/")
    add("├── README.md            # this file — process overview + pending findings")
    add("├── REVIEW-PROCESS.md     # how to perform a review and track findings")
    add("├── regen-readme.py       # regenerates this README from the findings files")
    add("├── _template/findings.md # copy-this template for a module review")
    add("└── <Module>/findings.md  # one folder per src/ project")
    add("```")
    add("")
    add("## Baseline review — 2026-05-16")
    add("")
    add("All 19 modules were reviewed at commit `9c60592` (241 findings: 6 Critical, 46 High,")
    add("100 Medium, 89 Low). The tables below track what remains **open** as findings are")
    add("resolved and re-triaged; findings discovered after the baseline are appended to their")
    add("module file and counted in **Total**.")
    add("")
    add("| Severity | Open findings |")
    add("|----------|---------------|")
    for sev in ("Critical", "High", "Medium", "Low"):
        add(f"| {sev} | {severity_total(sev)} |")
    add(f"| **Total** | **{len(pending)}** |")
    add("")
    add("## Module Status")
    add("")
    add("| Module | Last reviewed | Commit | Open (C/H/M/L) | Open | Total |")
    add("|--------|---------------|--------|----------------|------|-------|")
    for module in modules:
        counts = [open_count(module, s) for s in ("Critical", "High", "Medium", "Low")]
        add(f"| [{module}]({module}/findings.md) | 2026-05-16 | `9c60592` "
            f"| {counts[0]}/{counts[1]}/{counts[2]}/{counts[3]} "
            f"| {sum(counts)} | {len(per_module[module])} |")
    add("")
    add("## Pending Findings")
    add("")
    add("Every `Open` / `In Progress` finding across all modules, highest severity first.")
    add("Resolved findings drop off this list but remain recorded in their module's")
    add("`findings.md` (see [REVIEW-PROCESS.md](REVIEW-PROCESS.md) §4–§5). Full detail —")
    add("description, location, recommendation — lives in the module's `findings.md`.")
    add("")
    for sev in ("Critical", "High", "Medium", "Low"):
        rows = [f for f in pending if f[2] == sev]
        add(f"### {sev} ({len(rows)})")
        add("")
        if not rows:
            add("_None open._")
            add("")
            continue
        add("| ID | Module | Title |")
        add("|----|--------|-------|")
        for module, fid, _, title, _ in rows:
            add(f"| {fid} | [{module}]({module}/findings.md) | {title} |")
        add("")

    return "\n".join(lines)


def main():
    check = "--check" in sys.argv[1:]
    modules = discover_modules()
    per_module = {m: parse_findings(m) for m in modules}
    content = build_readme(modules, per_module)

    readme_path = os.path.join(BASE, "README.md")
    pending = sum(1 for fs in per_module.values()
                  for f in fs if f[4] in PENDING_STATUSES)
    total = sum(len(fs) for fs in per_module.values())

    if check:
        current = open(readme_path).read() if os.path.exists(readme_path) else ""
        if current != content:
            print("README.md is stale — run: python3 code-reviews/regen-readme.py")
            sys.exit(1)
        print(f"README.md is up to date ({pending} pending / {total} total).")
        return

    open(readme_path, "w").write(content)
    print(f"README.md regenerated — {pending} pending, {total} total findings "
          f"across {len(modules)} modules.")


if __name__ == "__main__":
    main()
