#!/usr/bin/env python3
"""Agent for auditing container registry image security: scanning, signing, and SBOM."""

import boto3
import subprocess
import json
import os
import argparse
from datetime import datetime


def scan_image_trivy(image, severity="HIGH,CRITICAL", output_format="json"):
    """Scan a container image with Trivy for vulnerabilities."""
    print(f"[*] Scanning {image} with Trivy...")
    cmd = ["trivy", "image", "--severity", severity, "--format", output_format, image]
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
        if output_format == "json" and result.stdout:
            data = json.loads(result.stdout)
            total_vulns = 0
            for target in data.get("Results", []):
                vulns = target.get("Vulnerabilities", [])
                total_vulns += len(vulns)
                if vulns:
                    print(f"  Target: {target.get('Target', 'unknown')}: {len(vulns)} vulnerabilities")
                    for v in vulns[:5]:
                        print(f"    [{v.get('Severity', '?')}] {v.get('VulnerabilityID', '?')} "
                              f"- {v.get('PkgName', '?')} {v.get('InstalledVersion', '?')}")
            print(f"[*] Total vulnerabilities found: {total_vulns}")
            return data
        else:
            print(result.stdout)
            return None
    except FileNotFoundError:
        print("  [-] Trivy not installed. Install: brew install trivy")
        return None
    except subprocess.TimeoutExpired:
        print("  [-] Scan timed out")
        return None


def generate_sbom(image, output_format="spdx-json", output_file="sbom.json"):
    """Generate SBOM using Syft for a container image."""
    print(f"\n[*] Generating SBOM for {image}...")
    cmd = ["syft", image, "-o", output_format]
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
        if result.stdout:
            with open(output_file, "w") as f:
                f.write(result.stdout)
            data = json.loads(result.stdout)
            packages = data.get("packages", [])
            print(f"  [+] SBOM generated: {len(packages)} packages")
            print(f"  [+] Saved to {output_file}")
            return data
        return None
    except FileNotFoundError:
        print("  [-] Syft not installed. Install: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh")
        return None


def verify_image_signature(image, key_file=None):
    """Verify image signature using Cosign."""
    print(f"\n[*] Verifying signature for {image}...")
    cmd = ["cosign", "verify"]
    if key_file:
        cmd.extend(["--key", key_file])
    cmd.append(image)
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
        if result.returncode == 0:
            print(f"  [+] Signature VALID for {image}")
            return True
        else:
            print(f"  [-] Signature verification FAILED: {result.stderr.strip()}")
            return False
    except FileNotFoundError:
        print("  [-] Cosign not installed")
        return None


def audit_ecr_repository(repo_name, region="us-east-1"):
    """Audit an ECR repository for security configuration."""
    ecr = boto3.client("ecr", region_name=region)
    findings = []
    try:
        scan_config = ecr.describe_repositories(repositoryNames=[repo_name])["repositories"][0]
        repo_uri = scan_config["repositoryUri"]
        print(f"\n[*] Auditing ECR repository: {repo_name}")
        print(f"  URI: {repo_uri}")

        img_scan = scan_config.get("imageScanningConfiguration", {})
        if not img_scan.get("scanOnPush", False):
            findings.append({"check": "scan_on_push", "status": "FAIL", "detail": "Scan on push disabled"})
            print("  [!] Scan on push: DISABLED")
        else:
            print("  [+] Scan on push: ENABLED")

        mutability = scan_config.get("imageTagMutability", "MUTABLE")
        if mutability == "MUTABLE":
            findings.append({"check": "tag_immutability", "status": "FAIL", "detail": "Tags are mutable"})
            print("  [!] Tag immutability: MUTABLE (tags can be overwritten)")
        else:
            print("  [+] Tag immutability: IMMUTABLE")

        try:
            lifecycle = ecr.get_lifecycle_policy(repositoryName=repo_name)
            print("  [+] Lifecycle policy: CONFIGURED")
        except ecr.exceptions.LifecyclePolicyNotFoundException:
            findings.append({"check": "lifecycle_policy", "status": "FAIL", "detail": "No lifecycle policy"})
            print("  [!] Lifecycle policy: NOT CONFIGURED")

        images = ecr.list_images(repositoryName=repo_name, filter={"tagStatus": "UNTAGGED"})
        untagged = len(images.get("imageIds", []))
        if untagged > 0:
            print(f"  [!] Untagged images: {untagged}")

    except ecr.exceptions.RepositoryNotFoundException:
        print(f"  [-] Repository '{repo_name}' not found")
    return findings


def get_ecr_scan_findings(repo_name, tag="latest", region="us-east-1"):
    """Get vulnerability scan findings for an ECR image."""
    ecr = boto3.client("ecr", region_name=region)
    try:
        response = ecr.describe_image_scan_findings(
            repositoryName=repo_name, imageId={"imageTag": tag},
        )
        findings = response.get("imageScanFindings", {})
        severity_counts = findings.get("findingSeverityCounts", {})
        print(f"\n[*] ECR scan findings for {repo_name}:{tag}")
        for sev, count in sorted(severity_counts.items()):
            print(f"  [{sev}] {count}")
        vuln_findings = findings.get("findings", [])
        for v in vuln_findings[:10]:
            print(f"  - {v.get('name', '?')}: {v.get('description', '')[:100]}")
        return findings
    except Exception as e:
        print(f"  [-] Error: {e}")
        return {}


def full_audit(image, repo_name=None, region="us-east-1", output_dir="."):
    """Run complete container image security audit."""
    print("[*] Starting container image security audit...")
    os.makedirs(output_dir, exist_ok=True)
    report = {"audit_date": datetime.now().isoformat(), "image": image}

    scan_results = scan_image_trivy(image)
    report["trivy_scan"] = scan_results

    sbom_path = os.path.join(output_dir, "sbom.json")
    sbom = generate_sbom(image, output_file=sbom_path)
    report["sbom_packages"] = len(sbom.get("packages", [])) if sbom else 0

    sig_valid = verify_image_signature(image)
    report["signature_valid"] = sig_valid

    if repo_name:
        ecr_findings = audit_ecr_repository(repo_name, region)
        report["ecr_audit"] = ecr_findings

    report_path = os.path.join(output_dir, "container_security_report.json")
    with open(report_path, "w") as f:
        json.dump(report, f, indent=2, default=str)
    print(f"\n[*] Full report saved to {report_path}")


def main():
    parser = argparse.ArgumentParser(description="Container Registry Image Security Agent")
    parser.add_argument("action", choices=["scan", "sbom", "verify", "ecr-audit", "ecr-findings", "full-audit"])
    parser.add_argument("--image", help="Container image reference")
    parser.add_argument("--repo", help="ECR repository name")
    parser.add_argument("--tag", default="latest", help="Image tag for ECR scan results")
    parser.add_argument("--key", help="Cosign public key file")
    parser.add_argument("--region", default="us-east-1")
    parser.add_argument("-o", "--output", default=".")
    args = parser.parse_args()

    if args.action == "scan":
        scan_image_trivy(args.image)
    elif args.action == "sbom":
        generate_sbom(args.image)
    elif args.action == "verify":
        verify_image_signature(args.image, args.key)
    elif args.action == "ecr-audit":
        audit_ecr_repository(args.repo, args.region)
    elif args.action == "ecr-findings":
        get_ecr_scan_findings(args.repo, args.tag, args.region)
    elif args.action == "full-audit":
        full_audit(args.image, args.repo, args.region, args.output)


if __name__ == "__main__":
    main()
