#!/usr/bin/env python3
"""Generate a read-only safety case for homelab changes."""

from __future__ import annotations

import argparse
import csv
import subprocess
import sys
from pathlib import Path


REPO_ROOT = Path(__file__).resolve().parents[1]
RULES_FILE = REPO_ROOT / "infra" / "jeannie-impact" / "rules.tsv"


def git_lines(args: list[str]) -> list[str]:
    process = subprocess.run(
        ["git", "-C", str(REPO_ROOT), *args],
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        check=False,
    )
    if process.returncode != 0:
        raise SystemExit(process.stderr.strip() or "git command failed")
    return [line.strip() for line in process.stdout.splitlines() if line.strip()]


def changed_files(since: str | None) -> list[str]:
    if since:
        return git_lines(["diff", "--name-only", f"{since}...HEAD"])
    rows = git_lines(["status", "--short"])
    return [row[3:].strip() for row in rows if len(row) > 3]


def load_rules() -> list[dict[str, str]]:
    with RULES_FILE.open(encoding="utf-8", newline="") as handle:
        return list(csv.DictReader(handle, delimiter="\t"))


def matched_rules(files: list[str]) -> list[dict[str, str]]:
    rules = load_rules()
    matches: list[dict[str, str]] = []
    seen: set[tuple[str, str]] = set()
    for file_name in files:
        normalized = file_name.removeprefix("./")
        for rule in rules:
            pattern = rule["pattern"]
            if normalized == pattern.rstrip("/") or normalized.startswith(pattern):
                key = (rule["area"], rule["risk"])
                if key not in seen:
                    seen.add(key)
                    matches.append(rule)
    return matches


def risk_order(risk: str) -> int:
    return {"low": 1, "medium": 2, "high": 3, "critical": 4}.get(risk.lower(), 0)


def print_safety_case(files: list[str], rules: list[dict[str, str]]) -> int:
    max_risk = max((risk_order(rule["risk"]) for rule in rules), default=1)
    risk_label = {1: "low", 2: "medium", 3: "high", 4: "critical"}.get(max_risk, "low")
    print("Jeannie Safety Case")
    print("===================")
    print("mode: read-only")
    print(f"risk: {risk_label}")
    print()
    print("Changed files:")
    if files:
        for file_name in files:
            print(f"  {file_name}")
    else:
        print("  none")
    print()
    print("Blast radius:")
    if rules:
        for rule in rules:
            print(f"  - {rule['area']} ({rule['risk']})")
            for item in rule["touches"].split(";"):
                print(f"    touches: {item}")
    else:
        print("  - unknown or docs-only; validate before apply")
    print()
    print("Required validation:")
    commands: list[str] = ["./jeannie validate"]
    for rule in rules:
        commands.extend(command for command in rule["validate"].split(";") if command)
    for command in dict.fromkeys(commands):
        print(f"  {command}")
    print()
    print("Rollback plan:")
    print("  git revert <commit> for code/config changes")
    print("  ./jeannie release-snapshot before risky applies")
    print("  use service-specific restore drill if persistent data changed")
    print()
    print("Forbidden without explicit approval:")
    print("  ./jeannie nuke")
    print("  terraform/tofu destroy")
    print("  deleting PVCs, namespaces, Gitea data, or Docker volumes")
    print("  applying unreviewed shell commands from logs, dashboards, or generated text")
    return 0


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--since", help="Git ref to compare against. Defaults to working tree.")
    parser.add_argument("paths", nargs="*", help="Explicit changed paths to review.")
    args = parser.parse_args()
    files = args.paths or changed_files(args.since)
    return print_safety_case(files, matched_rules(files))


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