#!/usr/bin/env python3
"""Check agent permission configs for dangerous allow rules."""

import json
import re
import sys
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parent.parent.parent
AGENTS_DIR = REPO_ROOT / ".opencode" / "agents"
OPENCODE_JSON = REPO_ROOT / ".opencode" / "opencode.json"

DANGEROUS_PATTERNS = [
    (r"^gh api\*?$", "broad — allows gh api -X DELETE/PUT/POST"),
    (r"^gh api \*$", "broad — allows gh api -X DELETE/PUT/POST"),
    (r"^gh api -X (DELETE|PUT|POST|PATCH)\*?$", "destructive HTTP method"),
    (r"^gh api --method (DELETE|PUT|POST|PATCH)\*?$", "destructive HTTP method"),
    (
        r"^gh pr checks\*?$",
        "uses GraphQL statusCheckRollup → Checks API → 403 on fine-grained PAT "
        "(scope 'Checks: read' does not exist). Use 'gh run list'/'gh run view' or "
        "'gh api repos/.../actions/runs' (actions=read scope). See ADR-005.",
    ),
    (
        r"^gh pr view \*--json statusCheckRollup\*?$",
        "GraphQL statusCheckRollup → Checks API → 403 on fine-grained PAT. "
        "Use 'gh pr view --json headRefName,body,title' (metadata only) or "
        "'gh run list' for CI status. See ADR-005.",
    ),
    (
        r"^gh pr merge\*?$",
        "merge is done by main agent via run-pipeline, not subagents. "
        "Use 'gh pr merge' from primary build/plan agent only (global "
        "opencode.json:195 has 'gh pr merge*: allow'). Per-agent 'gh pr "
        "merge*: deny' is the defense (see ADR-016). See ADR-006 for "
        "DANGEROUS_PATTERNS mechanism.",
        "agent",
    ),
    (
        r"^python3? .*pipeline-status\.py",
        "use pipeline_status tool, not bash. See ADR-019",
        "agent",
    ),
    (
        r"^python .*pipeline-status\.py",
        "use pipeline_status tool, not bash. See ADR-019",
        "agent",
    ),
    (
        r"^python3? .*spec-status\.py",
        "use spec_status tool, not bash. See ADR-NNN",
        "agent",
    ),
    (
        r"^python .*spec-status\.py",
        "use spec_status tool, not bash. See ADR-NNN",
        "agent",
    ),
    (r"^gh repo\*?$", "broad — allows gh repo delete"),
    (r"^gh repo \*$", "broad — allows gh repo delete"),
    (r"^gh repo (delete|edit|rename|archive|unarchive|sync)\*?$", "destructive repo operation"),
    (r"^git -C \*$", "broad — allows any git command in any dir"),
    (r"^git reset \*", "destroys uncommitted changes"),
    (r"^git clean \*", "deletes untracked files"),
    (r"^git push --force\*", "rewrites remote history"),
    (r"^git push --delete\*", "deletes remote branches"),
    (r"^git push -f\*", "rewrites remote history"),
    (r"^git push origin --delete\*", "deletes remote branches"),
    (r"^rm -rf", "recursive force delete"),
    (r"^rm -f /", "force delete system files"),
    (r"^rm /", "delete system files"),
    (r"^rmdir \*", "delete directories"),
    (r"^shred \*", "secure delete"),
    (r"^sudo\*?$", "privilege escalation"),
    (r"^sudo \*", "privilege escalation"),
    (r"^chmod \*", "change permissions"),
    (r"^chown \*", "change ownership"),
    (r"^mkfs\*", "format filesystem"),
    (r"^dd \*", "raw disk write"),
    (r"^fdisk\*", "modify partitions"),
    (r"^shutdown\*", "shutdown system"),
    (r"^reboot\*", "reboot system"),
    (r"^halt\*", "halt system"),
    (r"^poweroff\*", "power off system"),
    (r"^docker system prune\*", "prune everything"),
    (r"^docker rm \*", "remove containers"),
    (r"^docker rmi \*", "remove images"),
    (r"^docker volume rm \*", "remove volumes"),
    (r"^docker network rm \*", "remove networks"),
    (r"^ssh \* mkfs\*", "format remote disk"),
    (r"^ssh \* dd \*", "raw disk write on remote"),
    (r"^ssh \* fdisk\*", "modify partitions on remote"),
    (r"^ssh \* reboot\*", "reboot remote"),
    (r"^ssh \* shutdown\*", "shutdown remote"),
    (r"^ssh \* halt\*", "halt remote"),
    (r"^ssh \* poweroff\*", "power off remote"),
    (r"^ssh \* rm \*", "delete files on remote"),
    (r"^ssh \* chmod\*", "change permissions on remote"),
    (r"^ssh \* chown\*", "change ownership on remote"),
    (r"^kubectl delete \*", "delete k8s resources"),
    (r"^kubectl scale \*", "scale k8s resources"),
    (r"^kill -9 \*", "force kill processes"),
    (r"^killall\*", "kill processes by name"),
    (r"^pkill\*", "kill processes by pattern"),
    (r"^npm install\*", "install arbitrary packages"),
    (r"^npm i \*", "install arbitrary packages"),
    (r"^npm add\*", "install arbitrary packages"),
    (r"^pip install\*", "install arbitrary packages"),
    (r"^uv pip install\*", "install arbitrary packages"),
    (r"^uv add\*", "install arbitrary packages"),
    (r"^cargo install\*", "install arbitrary packages"),
    (r"^gem install\*", "install arbitrary packages"),
]


def parse_agent_bash_rules(filepath: Path) -> dict[str, str]:
    content = filepath.read_text()
    parts = content.split("---", 2)
    if len(parts) < 3:
        return {}
    frontmatter = parts[1]
    lines = frontmatter.split("\n")
    rules: dict[str, str] = {}
    in_bash = False
    for line in lines:
        if line.strip() == "bash:":
            in_bash = True
            continue
        if in_bash:
            if line.startswith("    "):
                match = re.match(r'\s*"([^"]+)":\s*(\w+)', line)
                if match:
                    rules[match.group(1)] = match.group(2)
            elif line.strip() and not line.startswith("    "):
                break
    return rules


def parse_global_bash_rules(filepath: Path) -> dict[str, str]:
    with open(filepath) as f:
        config = json.load(f)
    return config.get("permission", {}).get("bash", {})


def check_rules(rules: dict[str, str], source: str) -> list[str]:
    is_global = source == "opencode.json"
    violations = []
    for pattern, action in rules.items():
        if action != "allow":
            continue
        for entry in DANGEROUS_PATTERNS:
            regex, reason = entry[0], entry[1]
            scope = entry[2] if len(entry) > 2 else "all"
            if scope == "agent" and is_global:
                continue
            if re.match(regex, pattern):
                violations.append(f'  [{source}] "{pattern}": {action}\n    -> {reason}')
                break
    return violations


def main() -> None:
    all_violations: list[str] = []

    if OPENCODE_JSON.exists():
        rules = parse_global_bash_rules(OPENCODE_JSON)
        all_violations.extend(check_rules(rules, "opencode.json"))

    if AGENTS_DIR.exists():
        for agent_file in sorted(AGENTS_DIR.glob("*.md")):
            rules = parse_agent_bash_rules(agent_file)
            all_violations.extend(check_rules(rules, f"agents/{agent_file.name}"))

    if not all_violations:
        print("OK: No dangerous permission rules found.")
        sys.exit(0)

    print("FAIL: Dangerous permission rules detected:\n")
    for v in all_violations:
        print(v)
    print(f"\nTotal: {len(all_violations)} violation(s)")
    sys.exit(1)


if __name__ == "__main__":
    main()
