#!/usr/bin/env python3
"""Vulnerability SLA breach alerting agent.

Monitors vulnerability remediation timelines and generates alerts when
SLA breaches occur or are imminent. Supports webhook notifications
(Slack, Teams, PagerDuty), email alerts, and escalation workflows.
"""
import argparse
import json
import os
import sys
from datetime import datetime, timezone, timedelta

try:
    import requests
except ImportError:
    requests = None


DEFAULT_SLA_DAYS = {"CRITICAL": 7, "HIGH": 30, "MEDIUM": 90, "LOW": 180}


def load_vulnerabilities(source_path):
    """Load vulnerability data from JSON file."""
    with open(source_path, "r") as f:
        data = json.load(f)
    if isinstance(data, list):
        return data
    return data.get("vulnerabilities", data.get("findings", []))


def check_sla_breaches(vulns, sla_days=None, warn_days_before=7):
    """Check for SLA breaches and upcoming deadlines."""
    if sla_days is None:
        sla_days = DEFAULT_SLA_DAYS

    now = datetime.now(timezone.utc)
    breaches = []
    warnings = []

    for vuln in vulns:
        status = (vuln.get("status") or vuln.get("state") or "open").lower()
        if status not in ("open", "new", "active", "unresolved"):
            continue

        severity = (vuln.get("severity") or "MEDIUM").upper()
        target_days = sla_days.get(severity, 90)

        disc_str = (vuln.get("discovered_date") or vuln.get("first_found") or
                    vuln.get("discovered") or "")
        try:
            if "T" in disc_str:
                discovered = datetime.fromisoformat(disc_str.replace("Z", "+00:00"))
            elif disc_str:
                discovered = datetime.strptime(disc_str[:10], "%Y-%m-%d").replace(tzinfo=timezone.utc)
            else:
                continue
        except (ValueError, TypeError):
            continue

        deadline = discovered + timedelta(days=target_days)
        days_remaining = (deadline - now).days

        vuln_id = vuln.get("id") or vuln.get("cve_id") or vuln.get("vulnerability_id") or "unknown"
        asset = vuln.get("asset") or vuln.get("host") or vuln.get("ip") or "unknown"
        title = vuln.get("title") or vuln.get("name") or "Unknown"

        alert_entry = {
            "id": vuln_id,
            "severity": severity,
            "asset": asset,
            "title": title[:80],
            "discovered": disc_str[:10],
            "deadline": deadline.isoformat()[:10],
            "days_remaining": days_remaining,
            "sla_target_days": target_days,
        }

        if days_remaining < 0:
            alert_entry["alert_type"] = "BREACH"
            alert_entry["overdue_days"] = abs(days_remaining)
            breaches.append(alert_entry)
        elif days_remaining <= warn_days_before:
            alert_entry["alert_type"] = "WARNING"
            warnings.append(alert_entry)

    breaches.sort(key=lambda x: -x.get("overdue_days", 0))
    warnings.sort(key=lambda x: x.get("days_remaining", 999))
    return breaches, warnings


def send_slack_alert(webhook_url, breaches, warnings):
    """Send SLA breach alert to Slack via webhook."""
    if not requests:
        print("[!] requests library required for Slack alerts", file=sys.stderr)
        return False

    blocks = [
        {"type": "header", "text": {"type": "plain_text",
         "text": f"Vulnerability SLA Alert - {len(breaches)} Breaches, {len(warnings)} Warnings"}},
    ]

    if breaches:
        breach_text = "*SLA BREACHES (Immediate Action Required):*\n"
        for b in breaches[:10]:
            breach_text += (f"- [{b['severity']}] `{b['id']}` on {b['asset']} - "
                           f"*{b['overdue_days']}d overdue*\n")
        blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": breach_text}})

    if warnings:
        warn_text = "*Approaching SLA Deadline:*\n"
        for w in warnings[:10]:
            warn_text += (f"- [{w['severity']}] `{w['id']}` on {w['asset']} - "
                         f"{w['days_remaining']}d remaining\n")
        blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": warn_text}})

    payload = {"blocks": blocks}
    resp = requests.post(webhook_url, json=payload, timeout=15)
    if resp.status_code == 200:
        print(f"[+] Slack alert sent successfully")
        return True
    else:
        print(f"[!] Slack alert failed: {resp.status_code}", file=sys.stderr)
        return False


def send_teams_alert(webhook_url, breaches, warnings):
    """Send SLA breach alert to Microsoft Teams via webhook."""
    if not requests:
        return False

    facts = []
    for b in breaches[:5]:
        facts.append({"name": f"[BREACH] {b['id']}", "value": f"{b['severity']} - {b['overdue_days']}d overdue on {b['asset']}"})
    for w in warnings[:5]:
        facts.append({"name": f"[WARNING] {w['id']}", "value": f"{w['severity']} - {w['days_remaining']}d left on {w['asset']}"})

    payload = {
        "@type": "MessageCard",
        "themeColor": "FF0000" if breaches else "FFA500",
        "summary": f"SLA Alert: {len(breaches)} breaches, {len(warnings)} warnings",
        "sections": [{
            "activityTitle": "Vulnerability SLA Alert",
            "facts": facts,
        }],
    }
    resp = requests.post(webhook_url, json=payload, timeout=15)
    return resp.status_code == 200


def send_pagerduty_alert(routing_key, breaches):
    """Send PagerDuty incident for critical SLA breaches."""
    if not requests or not breaches:
        return False

    critical_breaches = [b for b in breaches if b["severity"] == "CRITICAL"]
    if not critical_breaches:
        return False

    payload = {
        "routing_key": routing_key,
        "event_action": "trigger",
        "payload": {
            "summary": f"{len(critical_breaches)} CRITICAL vulnerability SLA breaches",
            "severity": "critical",
            "source": "vulnerability-sla-agent",
            "custom_details": {
                "breaches": critical_breaches[:5],
                "total_critical_breaches": len(critical_breaches),
            },
        },
    }
    resp = requests.post(
        "https://events.pagerduty.com/v2/enqueue",
        json=payload, timeout=15,
    )
    if resp.status_code == 202:
        print(f"[+] PagerDuty incident created")
        return True
    return False


def format_summary(breaches, warnings):
    """Print alert summary."""
    print(f"\n{'='*60}")
    print(f"  Vulnerability SLA Breach Alert Report")
    print(f"{'='*60}")
    print(f"  SLA Breaches  : {len(breaches)}")
    print(f"  SLA Warnings  : {len(warnings)}")

    if breaches:
        critical = sum(1 for b in breaches if b["severity"] == "CRITICAL")
        high = sum(1 for b in breaches if b["severity"] == "HIGH")
        print(f"    Critical breaches: {critical}")
        print(f"    High breaches    : {high}")

        print(f"\n  Breached Vulnerabilities:")
        for b in breaches[:15]:
            print(f"    [{b['severity']:8s}] {b['id']:20s} | {b['asset']:20s} | "
                  f"{b['overdue_days']}d overdue (deadline: {b['deadline']})")

    if warnings:
        print(f"\n  Approaching Deadline:")
        for w in warnings[:10]:
            print(f"    [{w['severity']:8s}] {w['id']:20s} | {w['asset']:20s} | "
                  f"{w['days_remaining']}d remaining")


def main():
    parser = argparse.ArgumentParser(description="Vulnerability SLA breach alerting agent")
    parser.add_argument("--source", required=True, help="Vulnerability data JSON file")
    parser.add_argument("--sla-critical", type=int, default=7)
    parser.add_argument("--sla-high", type=int, default=30)
    parser.add_argument("--sla-medium", type=int, default=90)
    parser.add_argument("--sla-low", type=int, default=180)
    parser.add_argument("--warn-days", type=int, default=7, help="Warn N days before deadline")
    parser.add_argument("--slack-webhook", help="Slack webhook URL for alerts")
    parser.add_argument("--teams-webhook", help="Teams webhook URL for alerts")
    parser.add_argument("--pagerduty-key", help="PagerDuty routing key for critical breaches")
    parser.add_argument("--output", "-o", help="Output JSON report")
    parser.add_argument("--verbose", "-v", action="store_true")
    args = parser.parse_args()

    sla_days = {
        "CRITICAL": args.sla_critical, "HIGH": args.sla_high,
        "MEDIUM": args.sla_medium, "LOW": args.sla_low,
    }

    vulns = load_vulnerabilities(args.source)
    print(f"[*] Loaded {len(vulns)} vulnerabilities")

    breaches, warnings = check_sla_breaches(vulns, sla_days, args.warn_days)
    format_summary(breaches, warnings)

    alerts_sent = []
    if args.slack_webhook and (breaches or warnings):
        if send_slack_alert(args.slack_webhook, breaches, warnings):
            alerts_sent.append("slack")
    if args.teams_webhook and (breaches or warnings):
        if send_teams_alert(args.teams_webhook, breaches, warnings):
            alerts_sent.append("teams")
    if args.pagerduty_key and breaches:
        if send_pagerduty_alert(args.pagerduty_key, breaches):
            alerts_sent.append("pagerduty")

    report = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "tool": "SLA Breach Alerting",
        "sla_targets": sla_days,
        "breaches": breaches,
        "warnings": warnings,
        "alerts_sent": alerts_sent,
    }

    if args.output:
        with open(args.output, "w") as f:
            json.dump(report, f, indent=2)
        print(f"\n[+] Report saved to {args.output}")
    elif args.verbose:
        print(json.dumps(report, indent=2))


if __name__ == "__main__":
    main()
