#!/usr/bin/env python3
"""Privileged Account Access Review agent — audits privileged accounts for
compliance with least-privilege and periodic recertification requirements."""

import argparse
import csv
import json
from datetime import datetime, timedelta
from pathlib import Path


def load_accounts(csv_path: str) -> list[dict]:
    """Load privileged account inventory from CSV."""
    with open(csv_path, newline="", encoding="utf-8") as fh:
        reader = csv.DictReader(fh)
        return list(reader)


def check_stale_accounts(accounts: list[dict], max_days: int = 90) -> list[dict]:
    """Flag accounts not used within max_days."""
    findings = []
    cutoff = datetime.utcnow() - timedelta(days=max_days)
    for acct in accounts:
        last_used = acct.get("last_used", "")
        if not last_used:
            findings.append({"account": acct.get("username", ""), "issue": "no_last_used_date",
                             "severity": "high", "detail": "Account has no recorded last-used date"})
            continue
        try:
            used_dt = datetime.strptime(last_used, "%Y-%m-%d")
            if used_dt < cutoff:
                findings.append({"account": acct.get("username", ""),
                                 "issue": "stale_account", "severity": "high",
                                 "detail": f"Last used {last_used}, exceeds {max_days}-day threshold"})
        except ValueError:
            findings.append({"account": acct.get("username", ""), "issue": "invalid_date",
                             "severity": "medium", "detail": f"Cannot parse last_used: {last_used}"})
    return findings


def check_shared_accounts(accounts: list[dict]) -> list[dict]:
    """Detect shared/generic privileged accounts."""
    shared_patterns = ["admin", "root", "service", "svc_", "shared", "generic", "temp"]
    findings = []
    for acct in accounts:
        uname = acct.get("username", "").lower()
        owner = acct.get("owner", "").strip()
        for pat in shared_patterns:
            if pat in uname and not owner:
                findings.append({"account": acct.get("username", ""),
                                 "issue": "shared_account_no_owner", "severity": "critical",
                                 "detail": f"Appears shared (matches '{pat}') with no assigned owner"})
                break
    return findings


def check_excessive_privileges(accounts: list[dict]) -> list[dict]:
    """Flag accounts with overly broad privilege sets."""
    high_risk_roles = {"domain admin", "enterprise admin", "schema admin",
                       "global admin", "super admin", "root"}
    findings = []
    for acct in accounts:
        roles = {r.strip().lower() for r in acct.get("roles", "").split(";")}
        overlap = roles & high_risk_roles
        if overlap:
            findings.append({"account": acct.get("username", ""),
                             "issue": "excessive_privilege", "severity": "critical",
                             "detail": f"Holds high-risk roles: {', '.join(sorted(overlap))}"})
    return findings


def check_recertification(accounts: list[dict], cert_interval_days: int = 180) -> list[dict]:
    """Flag accounts overdue for recertification."""
    cutoff = datetime.utcnow() - timedelta(days=cert_interval_days)
    findings = []
    for acct in accounts:
        cert_date = acct.get("last_certified", "")
        if not cert_date:
            findings.append({"account": acct.get("username", ""),
                             "issue": "never_certified", "severity": "critical",
                             "detail": "Account has never been certified"})
            continue
        try:
            cert_dt = datetime.strptime(cert_date, "%Y-%m-%d")
            if cert_dt < cutoff:
                findings.append({"account": acct.get("username", ""),
                                 "issue": "overdue_recertification", "severity": "high",
                                 "detail": f"Last certified {cert_date}, exceeds {cert_interval_days}-day cycle"})
        except ValueError:
            pass
    return findings


def generate_report(accounts: list[dict], stale_days: int, cert_days: int) -> dict:
    """Run all checks and produce a consolidated JSON report."""
    findings = []
    findings.extend(check_stale_accounts(accounts, stale_days))
    findings.extend(check_shared_accounts(accounts))
    findings.extend(check_excessive_privileges(accounts))
    findings.extend(check_recertification(accounts, cert_days))

    severity_counts = {}
    for f in findings:
        severity_counts[f["severity"]] = severity_counts.get(f["severity"], 0) + 1

    return {
        "report": "privileged_account_access_review",
        "generated_at": datetime.utcnow().isoformat() + "Z",
        "total_accounts": len(accounts),
        "total_findings": len(findings),
        "severity_summary": severity_counts,
        "findings": findings,
    }


def main():
    parser = argparse.ArgumentParser(description="Privileged Account Access Review Agent")
    parser.add_argument("--input", required=True, help="CSV file with privileged account inventory")
    parser.add_argument("--stale-days", type=int, default=90, help="Max days of inactivity (default: 90)")
    parser.add_argument("--cert-days", type=int, default=180, help="Recertification interval in days (default: 180)")
    parser.add_argument("--output", help="Output JSON file path")
    args = parser.parse_args()

    accounts = load_accounts(args.input)
    report = generate_report(accounts, args.stale_days, args.cert_days)

    output = json.dumps(report, indent=2)
    if args.output:
        Path(args.output).write_text(output, encoding="utf-8")
        print(f"Report written to {args.output}")
    else:
        print(output)


if __name__ == "__main__":
    main()
