#!/usr/bin/env python3
"""Cloud Security Posture Management (CSPM) agent across AWS, Azure, and GCP."""

import json
import argparse
import subprocess
from datetime import datetime
from collections import Counter

try:
    import boto3
    from botocore.exceptions import ClientError
except ImportError:
    boto3 = None


def run_prowler_scan(provider="aws", compliance="cis_level1", output_format="json"):
    """Run Prowler CSPM scan against a cloud provider."""
    cmd = ["prowler", provider, "--compliance", compliance,
           "-M", output_format, "--output-directory", "/tmp/prowler-output"]
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
        return {"status": "completed", "returncode": result.returncode,
                "output_dir": "/tmp/prowler-output"}
    except FileNotFoundError:
        return {"error": "Prowler not installed. Run: pip install prowler"}
    except subprocess.TimeoutExpired:
        return {"error": "Prowler scan timed out after 10 minutes"}


def check_aws_security_posture(region="us-east-1"):
    """Run basic AWS security posture checks using boto3."""
    if boto3 is None:
        return {"error": "boto3 not installed"}
    findings = []

    s3 = boto3.client("s3", region_name=region)
    try:
        buckets = s3.list_buckets().get("Buckets", [])
        for bucket in buckets:
            name = bucket["Name"]
            try:
                pab = s3.get_public_access_block(Bucket=name)
                config = pab["PublicAccessBlockConfiguration"]
                if not all([config["BlockPublicAcls"], config["BlockPublicPolicy"],
                            config["IgnorePublicAcls"], config["RestrictPublicBuckets"]]):
                    findings.append({"check": "S3_PUBLIC_ACCESS", "resource": name,
                                     "severity": "HIGH", "status": "FAIL",
                                     "detail": "Public access block not fully enabled"})
            except ClientError:
                findings.append({"check": "S3_PUBLIC_ACCESS", "resource": name,
                                 "severity": "HIGH", "status": "FAIL",
                                 "detail": "No public access block configured"})
            try:
                s3.get_bucket_encryption(Bucket=name)
            except ClientError:
                findings.append({"check": "S3_ENCRYPTION", "resource": name,
                                 "severity": "MEDIUM", "status": "FAIL",
                                 "detail": "Default encryption not enabled"})
    except ClientError as e:
        findings.append({"check": "S3_ACCESS", "status": "ERROR", "detail": str(e)})

    iam = boto3.client("iam", region_name=region)
    try:
        acct_summary = iam.get_account_summary()["SummaryMap"]
        if acct_summary.get("AccountMFAEnabled", 0) == 0:
            findings.append({"check": "ROOT_MFA", "resource": "root-account",
                             "severity": "CRITICAL", "status": "FAIL",
                             "detail": "Root account MFA not enabled"})
        users = iam.list_users()["Users"]
        for user in users:
            keys = iam.list_access_keys(UserName=user["UserName"])["AccessKeyMetadata"]
            for key in keys:
                age = (datetime.utcnow() - key["CreateDate"].replace(tzinfo=None)).days
                if age > 90:
                    findings.append({"check": "IAM_KEY_ROTATION", "resource": user["UserName"],
                                     "severity": "MEDIUM", "status": "FAIL",
                                     "detail": f"Access key {key['AccessKeyId']} is {age} days old"})
    except ClientError as e:
        findings.append({"check": "IAM_ACCESS", "status": "ERROR", "detail": str(e)})

    ec2 = boto3.client("ec2", region_name=region)
    try:
        sgs = ec2.describe_security_groups()["SecurityGroups"]
        for sg in sgs:
            for rule in sg.get("IpPermissions", []):
                for ip_range in rule.get("IpRanges", []):
                    if ip_range.get("CidrIp") == "0.0.0.0/0":
                        port = rule.get("FromPort", "all")
                        if port in [22, 3389, 0, -1]:
                            findings.append({"check": "SG_OPEN_PORTS", "resource": sg["GroupId"],
                                             "severity": "HIGH", "status": "FAIL",
                                             "detail": f"Port {port} open to 0.0.0.0/0"})
    except ClientError as e:
        findings.append({"check": "EC2_ACCESS", "status": "ERROR", "detail": str(e)})

    ct = boto3.client("cloudtrail", region_name=region)
    try:
        trails = ct.describe_trails()["trailList"]
        if not trails:
            findings.append({"check": "CLOUDTRAIL_ENABLED", "resource": "account",
                             "severity": "CRITICAL", "status": "FAIL",
                             "detail": "No CloudTrail trails configured"})
        for trail in trails:
            status = ct.get_trail_status(Name=trail["TrailARN"])
            if not status.get("IsLogging"):
                findings.append({"check": "CLOUDTRAIL_LOGGING", "resource": trail["Name"],
                                 "severity": "CRITICAL", "status": "FAIL",
                                 "detail": "CloudTrail is not actively logging"})
    except ClientError as e:
        findings.append({"check": "CLOUDTRAIL_ACCESS", "status": "ERROR", "detail": str(e)})

    return findings


def generate_posture_report(findings):
    """Generate a CSPM posture report from findings."""
    severity_counts = Counter(f["severity"] for f in findings if f.get("severity"))
    check_counts = Counter(f["check"] for f in findings)
    fail_count = sum(1 for f in findings if f.get("status") == "FAIL")
    pass_rate = round((1 - fail_count / max(len(findings), 1)) * 100, 1)

    print(f"\n{'='*60}")
    print(f"  CSPM POSTURE REPORT")
    print(f"  Generated: {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC")
    print(f"{'='*60}\n")

    print(f"--- SUMMARY ---")
    print(f"  Total Checks: {len(findings)}")
    print(f"  Failed: {fail_count}")
    print(f"  Pass Rate: {pass_rate}%\n")

    print(f"--- BY SEVERITY ---")
    for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]:
        count = severity_counts.get(sev, 0)
        bar = "#" * count
        print(f"  {sev:<10} {count:>3} {bar}")

    print(f"\n--- FAILED CHECKS ---")
    for f in findings:
        if f.get("status") == "FAIL":
            print(f"  [{f['severity']}] {f['check']}: {f.get('resource', 'N/A')}")
            print(f"    {f.get('detail', '')}")

    print(f"\n{'='*60}\n")
    return {"total": len(findings), "failed": fail_count, "pass_rate": pass_rate,
            "by_severity": dict(severity_counts)}


def main():
    parser = argparse.ArgumentParser(description="CSPM Agent")
    parser.add_argument("--provider", default="aws", choices=["aws", "azure", "gcp"])
    parser.add_argument("--region", default="us-east-1")
    parser.add_argument("--prowler", action="store_true", help="Run Prowler scan")
    parser.add_argument("--scan", action="store_true", help="Run built-in posture scan")
    parser.add_argument("--output", help="Save report to JSON")
    args = parser.parse_args()

    if args.prowler:
        result = run_prowler_scan(args.provider)
        print(json.dumps(result, indent=2))
    elif args.scan:
        findings = check_aws_security_posture(args.region)
        report = generate_posture_report(findings)
        if args.output:
            with open(args.output, "w") as f:
                json.dump({"findings": findings, "summary": report}, f, indent=2, default=str)
            print(f"[+] Report saved to {args.output}")
    else:
        parser.print_help()


if __name__ == "__main__":
    main()
