#!/usr/bin/env python3
"""Suricata IPS Agent - manages rules, analyzes alerts, and tunes Suricata intrusion prevention."""

import json
import argparse
import logging
import os
import subprocess
from collections import defaultdict
from datetime import datetime

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)

EVE_LOG = os.environ.get("SURICATA_EVE_LOG", "/var/log/suricata/eve.json")
RULES_DIR = os.environ.get("SURICATA_RULES_DIR", "/etc/suricata/rules")


def parse_eve_alerts(log_path, limit=10000):
    """Parse Suricata EVE JSON log for alerts."""
    alerts = []
    try:
        with open(log_path) as f:
            for i, line in enumerate(f):
                if i >= limit:
                    break
                try:
                    event = json.loads(line)
                    if event.get("event_type") == "alert":
                        alerts.append({
                            "timestamp": event.get("timestamp", ""),
                            "src_ip": event.get("src_ip", ""),
                            "dest_ip": event.get("dest_ip", ""),
                            "src_port": event.get("src_port", 0),
                            "dest_port": event.get("dest_port", 0),
                            "proto": event.get("proto", ""),
                            "signature": event.get("alert", {}).get("signature", ""),
                            "signature_id": event.get("alert", {}).get("signature_id", 0),
                            "severity": event.get("alert", {}).get("severity", 0),
                            "category": event.get("alert", {}).get("category", ""),
                            "action": event.get("alert", {}).get("action", ""),
                        })
                except json.JSONDecodeError:
                    continue
    except FileNotFoundError:
        logger.warning("EVE log not found: %s", log_path)
    return alerts


def analyze_alert_distribution(alerts):
    """Analyze alert distribution by signature, severity, and source."""
    by_sig = defaultdict(int)
    by_severity = defaultdict(int)
    by_category = defaultdict(int)
    by_src = defaultdict(int)
    for alert in alerts:
        by_sig[alert["signature"]] += 1
        by_severity[alert["severity"]] += 1
        by_category[alert["category"]] += 1
        by_src[alert["src_ip"]] += 1
    return {
        "top_signatures": dict(sorted(by_sig.items(), key=lambda x: x[1], reverse=True)[:15]),
        "severity_distribution": dict(by_severity),
        "category_distribution": dict(sorted(by_category.items(), key=lambda x: x[1], reverse=True)[:10]),
        "top_source_ips": dict(sorted(by_src.items(), key=lambda x: x[1], reverse=True)[:10]),
    }


def identify_noisy_rules(alerts, threshold=500):
    """Identify rules that generate excessive alerts for tuning."""
    sig_counts = defaultdict(int)
    sig_info = {}
    for alert in alerts:
        sid = alert["signature_id"]
        sig_counts[sid] += 1
        if sid not in sig_info:
            sig_info[sid] = {"signature": alert["signature"], "category": alert["category"]}
    noisy = []
    for sid, count in sig_counts.items():
        if count >= threshold:
            noisy.append({"sid": sid, "count": count, **sig_info[sid]})
    return sorted(noisy, key=lambda x: x["count"], reverse=True)


def detect_attack_patterns(alerts):
    """Detect coordinated attack patterns from alert correlation."""
    src_dest_pairs = defaultdict(list)
    for alert in alerts:
        key = (alert["src_ip"], alert["dest_ip"])
        src_dest_pairs[key].append(alert)
    patterns = []
    for (src, dst), pair_alerts in src_dest_pairs.items():
        if len(pair_alerts) >= 20:
            sigs = list(set(a["signature_id"] for a in pair_alerts))
            patterns.append({
                "source": src, "target": dst,
                "alert_count": len(pair_alerts),
                "unique_signatures": len(sigs),
                "severity": "critical" if len(sigs) > 5 else "high",
                "pattern": "multi_stage_attack" if len(sigs) > 3 else "repeated_exploit",
            })
    return sorted(patterns, key=lambda x: x["alert_count"], reverse=True)


def check_suricata_status():
    """Check Suricata service status and configuration."""
    status_cmd = subprocess.run(["systemctl", "is-active", "suricata"], capture_output=True, text=True, timeout=120)
    rule_count_cmd = subprocess.run(["suricata", "--build-info"], capture_output=True, text=True, timeout=120)
    stats_cmd = subprocess.run(["suricatasc", "-c", "dump-counters"], capture_output=True, text=True, timeout=120)
    return {
        "service_active": status_cmd.stdout.strip() == "active",
        "build_info": rule_count_cmd.stdout[:200] if rule_count_cmd.returncode == 0 else "unavailable",
    }


def generate_report(alerts, status):
    distribution = analyze_alert_distribution(alerts)
    noisy = identify_noisy_rules(alerts)
    patterns = detect_attack_patterns(alerts)
    dropped = sum(1 for a in alerts if a["action"] == "blocked")
    report = {
        "timestamp": datetime.utcnow().isoformat(),
        "suricata_status": status,
        "total_alerts": len(alerts),
        "blocked_events": dropped,
        "block_rate": round(dropped / max(len(alerts), 1) * 100, 1),
        "alert_distribution": distribution,
        "noisy_rules_for_tuning": noisy[:10],
        "attack_patterns_detected": patterns[:10],
    }
    return report


def main():
    parser = argparse.ArgumentParser(description="Suricata IPS Analysis Agent")
    parser.add_argument("--eve-log", default=EVE_LOG, help="Path to EVE JSON log")
    parser.add_argument("--limit", type=int, default=50000, help="Max log lines to parse")
    parser.add_argument("--noisy-threshold", type=int, default=500, help="Alert count threshold for noisy rules")
    parser.add_argument("--output", default="suricata_ips_report.json")
    args = parser.parse_args()

    status = check_suricata_status()
    alerts = parse_eve_alerts(args.eve_log, args.limit)
    report = generate_report(alerts, status)
    with open(args.output, "w") as f:
        json.dump(report, f, indent=2, default=str)
    logger.info("Suricata: %d alerts, %.1f%% blocked, %d attack patterns",
                report["total_alerts"], report["block_rate"],
                len(report["attack_patterns_detected"]))
    print(json.dumps(report, indent=2, default=str))


if __name__ == "__main__":
    main()
