#!/usr/bin/env python3
"""Git Checklist Validator

A safe, vendor-neutral helper script for reviewing Git branch hygiene,
commit history, merge safety, test validation, and release readiness.

This script is intentionally non-destructive. It does not modify Git history,
checkout branches, merge code, or access credentials. It only inspects the
current repository state, prompts for checklist evidence, and can save a
simple Markdown report.

Typical uses:
  - Run before merge approval or release promotion.
  - Capture evidence for branch scope, history integrity, and rollback readiness.
  - Produce a shareable summary of pass/fail decisions and notes.

Examples:
  python git_checklist_validator.py
  python git_checklist_validator.py --report release-readiness.md
  python git_checklist_validator.py --json report.json --non-interactive
"""

from __future__ import annotations

import argparse
import json
import os
import subprocess
import sys
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import List, Optional


@dataclass
class ChecklistItem:
    phase: str
    item: str
    evidence: str = ""
    status: str = "pending"  # pass, fail, pending
    notes: str = ""


CHECKLIST: List[ChecklistItem] = [
    # Phase 1: Branch setup and scope control
    ChecklistItem("Branch setup", "Branch name includes ticket/change reference"),
    ChecklistItem("Branch setup", "Branch base is the intended parent/integration branch"),
    ChecklistItem("Branch setup", "Diff contains only the required change set"),
    ChecklistItem("Branch setup", "Branch owner and reviewer are documented"),
    ChecklistItem("Branch setup", "Merge target and release target are clear"),
    ChecklistItem("Branch setup", "Local and remote branch names match team convention"),

    # Phase 2: Commit hygiene and history integrity
    ChecklistItem("Commit hygiene", "Each commit message describes one logical change"),
    ChecklistItem("Commit hygiene", "Commit sequence is logical for review and testing"),
    ChecklistItem("Commit hygiene", "No secrets, generated artifacts, or large binaries are committed"),
    ChecklistItem("Commit hygiene", "Any force-push/amend/rewrite activity is documented"),
    ChecklistItem("Commit hygiene", "History strategy is reviewed (squash, preserve, or merge-as-is)"),
    ChecklistItem("Commit hygiene", "Branch can be reproduced from a clean checkout"),

    # Phase 3: Merge safety and conflict handling
    ChecklistItem("Merge safety", "Merge target is current and validated"),
    ChecklistItem("Merge safety", "Diff against merge base has no unresolved conflict markers"),
    ChecklistItem("Merge safety", "Conflict resolutions were intentional and reviewed when risk is high"),
    ChecklistItem("Merge safety", "Merged result was tested in a clean target-like environment"),
    ChecklistItem("Merge safety", "File-level conflict decisions were documented"),
    ChecklistItem("Merge safety", "Rollback ownership is assigned"),

    # Phase 4: Test validation and regression checks
    ChecklistItem("Test validation", "Test scope covers changed paths and likely failure modes"),
    ChecklistItem("Test validation", "Unit, integration, and smoke tests were reviewed as applicable"),
    ChecklistItem("Test validation", "Any skipped tests are documented with reason and owner"),
    ChecklistItem("Test validation", "Environment assumptions were validated"),
    ChecklistItem("Test validation", "Accepted failures are documented and not release blockers"),
    ChecklistItem("Test validation", "Test sign-off owner can explain and re-run results"),

    # Phase 5: Release readiness and rollback evidence
    ChecklistItem("Release readiness", "Release commit or tag matches tested branch content exactly"),
    ChecklistItem("Release readiness", "Release notes document user-visible/operator-visible changes"),
    ChecklistItem("Release readiness", "Config changes, migrations, or dependency updates are included"),
    ChecklistItem("Release readiness", "Rollback plan is documented and practical"),
    ChecklistItem("Release readiness", "Approval boundaries and release owner are clear"),
    ChecklistItem("Release readiness", "Required evidence is captured and stored"),
]


def run_git(args: List[str]) -> Optional[str]:
    try:
        result = subprocess.run(
            ["git", *args],
            check=True,
            capture_output=True,
            text=True,
        )
        return result.stdout.strip()
    except (subprocess.CalledProcessError, FileNotFoundError):
        return None


def in_git_repo() -> bool:
    return run_git(["rev-parse", "--is-inside-work-tree"]) == "true"


def current_branch() -> str:
    value = run_git(["branch", "--show-current"])
    return value or "unknown"


def current_commit() -> str:
    value = run_git(["rev-parse", "--short", "HEAD"])
    return value or "unknown"


def repo_root() -> str:
    value = run_git(["rev-parse", "--show-toplevel"])
    return value or os.getcwd()


def prompt_status(item: ChecklistItem) -> ChecklistItem:
    print(f"\n[{item.phase}] {item.item}")
    while True:
        choice = input("Status [p]ass / [f]ail / [s]kip: ").strip().lower()
        if choice in {"p", "pass"}:
            item.status = "pass"
            break
        if choice in {"f", "fail"}:
            item.status = "fail"
            break
        if choice in {"s", "skip"}:
            item.status = "pending"
            break
        print("Please enter pass, fail, or skip.")
    item.evidence = input("Evidence or command output summary (optional): ").strip()
    item.notes = input("Notes (optional): ").strip()
    return item


def summarize(items: List[ChecklistItem]) -> dict:
    counts = {"pass": 0, "fail": 0, "pending": 0}
    for item in items:
        counts[item.status] = counts.get(item.status, 0) + 1
    return counts


def render_markdown(items: List[ChecklistItem]) -> str:
    counts = summarize(items)
    lines = [
        "# Git Checklist Validation Report",
        "",
        f"- Repository root: `{repo_root()}`",
        f"- Branch: `{current_branch()}`",
        f"- Commit: `{current_commit()}`",
        f"- Pass: {counts['pass']} | Fail: {counts['fail']} | Pending: {counts['pending']}",
        "",
        "## Checklist Results",
    ]
    last_phase = None
    for item in items:
        if item.phase != last_phase:
            lines.append(f"\n### {item.phase}")
            last_phase = item.phase
        lines.append(f"- **{item.item}** — {item.status.upper()}")
        if item.evidence:
            lines.append(f"  - Evidence: {item.evidence}")
        if item.notes:
            lines.append(f"  - Notes: {item.notes}")
    return "\n".join(lines).strip() + "\n"


def save_text(path: Path, content: str) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(content, encoding="utf-8")


def main() -> int:
    parser = argparse.ArgumentParser(description="Git checklist validator for safe branching, merging, and release review.")
    parser.add_argument("--report", help="Write a Markdown report to this path.")
    parser.add_argument("--json", dest="json_path", help="Write a JSON report to this path.")
    parser.add_argument("--non-interactive", action="store_true", help="Do not prompt; output repository context only.")
    args = parser.parse_args()

    if not in_git_repo():
        print("Error: This script must be run inside a Git repository.", file=sys.stderr)
        return 2

    items = [ChecklistItem(**asdict(item)) for item in CHECKLIST]

    print("Git Checklist Validator")
    print(f"Repository: {repo_root()}")
    print(f"Branch: {current_branch()}")
    print(f"Commit: {current_commit()}")

    if not args.non_interactive:
        print("\nAnswer each checklist item using pass, fail, or skip.")
        for idx, item in enumerate(items):
            items[idx] = prompt_status(item)

    counts = summarize(items)
    print(f"\nSummary: {counts['pass']} pass, {counts['fail']} fail, {counts['pending']} pending")

    if args.report:
        report = render_markdown(items)
        save_text(Path(args.report), report)
        print(f"Markdown report written to {args.report}")

    if args.json_path:
        payload = {
            "repository": repo_root(),
            "branch": current_branch(),
            "commit": current_commit(),
            "summary": counts,
            "items": [asdict(item) for item in items],
        }
        save_text(Path(args.json_path), json.dumps(payload, indent=2))
        print(f"JSON report written to {args.json_path}")

    if counts["fail"] > 0:
        print("One or more checks failed. Review the evidence before merging or releasing.")
        return 1

    return 0


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