#!/usr/bin/env python3
"""PeerCortex Daily Audit Mailer — detailed report via localhost:25"""
import os, json, smtplib
from datetime import datetime, timezone, timedelta
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import make_msgid, formatdate

SMTP_HOST = "localhost"
SMTP_PORT = 25
MAIL_FROM = "ctx-report@fichtmueller.org"
MAIL_TO   = "rf@flexoptix.net"

def td(content, color=None, align="left", mono=False, bold=False):
    ff = "monospace" if mono else "sans-serif"
    fw = "font-weight:700;" if bold else ""
    cl = "color:%s;" % color if color else ""
    return '<td style="font-family:%s;font-size:11px;padding:3px 8px;text-align:%s;%s%s">%s</td>' % (ff, align, cl, fw, content)

def load_report():
    for delta in [0, 1]:
        d = (datetime.now(timezone.utc) - timedelta(days=delta)).strftime("%Y-%m-%d")
        try:
            with open("/var/log/peercortex/audit-%s.json" % d) as f:
                return json.load(f), d
        except FileNotFoundError:
            continue
    return None, datetime.now(timezone.utc).strftime("%Y-%m-%d")

def build(report, date_str):
    if not report:
        return ("<html><body style='background:#0f172a;color:#e2e8f0;padding:24px'><h2>No report for %s</h2></body></html>" % date_str,
                "PeerCortex Audit %s — no report" % date_str)

    counts  = report.get("counts", {})
    results = report.get("results", [])
    elapsed = report.get("elapsed_s", 0)
    pool    = report.get("pool_size", 0)
    ok   = counts.get("ok", 0)
    warn = counts.get("warning", 0)
    crit = counts.get("critical", 0)
    skip = counts.get("skip", 0)
    total = len(results)
    mins, secs = elapsed // 60, elapsed % 60

    if crit == 0 and warn == 0:
        sc, emoji, text, spfx = "#22c55e", "✅", "All Clear — no issues detected", "✅ All Clear"
    elif crit == 0:
        sc, emoji, text, spfx = "#f97316", "⚠️", "%d warning(s)" % warn, "⚠️ %d Warning(s)" % warn
    else:
        sc, emoji, text, spfx = "#ef4444", "🚨", "%d CRITICAL issue(s) — action required" % crit, "🚨 %d CRITICAL" % crit

    subject = "PeerCortex Daily Audit %s — %s" % (date_str, spfx)

    # Issues
    issue_results = [r for r in results if r.get("status") in ("critical", "warning")]
    issues_html = ""
    if issue_results:
        rows = ""
        for r in issue_results:
            asn = str(r["asn"])
            name = (r.get("data", {}).get("name") or "?")[:28]
            for sev, field, msg in r.get("issues", []):
                c = "#ef4444" if sev == "CRITICAL" else "#f97316"
                rows += "<tr style='border-bottom:1px solid #1e293b'>%s%s%s%s%s</tr>" % (
                    td("[%s]" % sev[:4], c, mono=True),
                    td("AS" + asn, "#94a3b8", mono=True),
                    td(name, "#e2e8f0"),
                    td(field, "#64748b", mono=True),
                    td(msg[:60], "#94a3b8"))
        issues_html = (
            "<h3 style='color:#f97316;font-size:12px;margin:24px 0 6px;text-transform:uppercase'>⚠ Issues (%d ASNs)</h3>" % len(issue_results) +
            "<table style='width:100%%;border-collapse:collapse;background:#111827;border-radius:6px;overflow:hidden'>%s</table>" % rows
        )

    # Skipped
    skip_results = [r for r in results if r.get("status") == "skip"]
    skipped_html = ""
    if skip_results:
        rows = ""
        for r in skip_results:
            asn = str(r["asn"])
            reason = r.get("issues", [["SKIP","","no response from PeerCortex"]])[0][2] if r.get("issues") else "no response from PeerCortex"
            url = "https://peercortex.org/?asn=" + asn
            rows += ("<tr style='border-bottom:1px solid #1e293b'>%s%s<td style='font-size:11px;padding:3px 8px'>"
                     "<a href='%s' style='color:#3b82f6;text-decoration:none'>→ lookup</a></td></tr>") % (
                td("AS" + asn, "#94a3b8", mono=True),
                td(reason, "#64748b"),
                url)
        skipped_html = (
            "<h3 style='color:#475569;font-size:12px;margin:24px 0 6px;text-transform:uppercase'>⏭ Skipped ASNs (%d) — no PeerCortex response</h3>" % skip +
            "<p style='color:#475569;font-size:11px;margin:0 0 8px'>Possible causes: ASN not in any routing table, very large prefix count, or temporary API timeout.</p>" +
            "<table style='width:100%%;border-collapse:collapse;background:#111827;border-radius:6px;overflow:hidden'>%s</table>" % rows
        )

    # Low RPKI
    rpki_bad = sorted(
        [r for r in results if r.get("status") != "skip" and r.get("data", {}).get("rpki_pct", 100) < 60],
        key=lambda r: r.get("data", {}).get("rpki_pct", 100)
    )[:8]
    rpki_html = ""
    if rpki_bad:
        rows = ""
        for r in rpki_bad:
            d = r.get("data", {})
            pct = d.get("rpki_pct", 0)
            bar = ("<div style='background:#1e293b;border-radius:3px;height:8px;width:100px;display:inline-block'>"
                   "<div style='background:#ef4444;border-radius:3px;height:8px;width:%d%%'></div></div>") % max(2, pct)
            rows += "<tr style='border-bottom:1px solid #1e293b'>%s%s%s<td style='padding:3px 8px'>%s</td>%s</tr>" % (
                td("AS" + str(r["asn"]), "#94a3b8", mono=True),
                td((d.get("name") or "?")[:28], "#e2e8f0"),
                td(d.get("rir", "?"), "#64748b", "center"),
                bar,
                td(str(pct) + "%", "#ef4444", "right", bold=True))
        rpki_html = (
            "<h3 style='color:#94a3b8;font-size:12px;margin:24px 0 6px;text-transform:uppercase'>📉 Low RPKI Coverage (&lt;60%%)</h3>" +
            "<table style='width:100%%;border-collapse:collapse;background:#111827;border-radius:6px;overflow:hidden'>%s</table>" % rows
        )

    # All ASNs table
    sorted_results = sorted([r for r in results if r.get("status") != "skip"], key=lambda x: x["asn"])
    header = ("<tr style='background:#1e293b'>%s%s%s%s%s%s%s%s%s%s%s</tr>" % (
        td("ASN", "#64748b", "right", mono=True),
        td("Name", "#64748b"),
        td("RIR", "#64748b", "center"),
        td("CC", "#64748b", "center"),
        td("Pfx", "#64748b", "right"),
        td("RIPE Stat", "#64748b", "right"),
        td("RPKI%", "#64748b", "right"),
        td("IX", "#64748b", "right"),
        td("PDB IX", "#64748b", "right"),
        td("Fac", "#64748b", "right"),
        td("Status", "#64748b", "center")))
    rows = ""
    for i, r in enumerate(sorted_results):
        d = r.get("data", {})
        bg = "#0f172a" if i % 2 == 0 else "#111827"
        st = r.get("status", "ok")
        if st == "critical":
            st_html = '<span style="color:#ef4444;font-weight:700">✗ CRIT</span>'
        elif st == "warning":
            st_html = '<span style="color:#f97316">⚠ WARN</span>'
        else:
            st_html = '<span style="color:#22c55e">✓</span>'
        pct = d.get("rpki_pct", 100)
        rpki_c = "#ef4444" if pct < 50 else "#f97316" if pct < 80 else "#94a3b8"
        ripe = str(d.get("ripe_stat_pfx", "—"))
        pdb  = str(d.get("pdb_ix_cnt", "—"))
        rows += "<tr style='background:%s'>%s%s%s%s%s%s%s%s%s%s<td style='font-size:11px;padding:3px 8px;text-align:center'>%s</td></tr>" % (
            bg,
            td("AS" + str(r["asn"]), "#64748b", "right", mono=True),
            td((d.get("name") or "?")[:28], "#e2e8f0"),
            td(d.get("rir", "?"), "#94a3b8", "center"),
            td(d.get("cc", "?"), "#94a3b8", "center"),
            td(str(d.get("pfx_total", 0)), "#94a3b8", "right", mono=True),
            td(ripe, "#64748b", "right", mono=True),
            td(str(pct) + "%", rpki_c, "right", mono=True),
            td(str(d.get("ix_cnt", 0)), "#94a3b8", "right", mono=True),
            td(pdb, "#64748b", "right", mono=True),
            td(str(d.get("fac_cnt", 0)), "#94a3b8", "right", mono=True),
            st_html)

    all_table = (
        "<h3 style='color:#94a3b8;font-size:12px;margin:24px 0 6px;text-transform:uppercase'>All Tested ASNs (%d)</h3>" % len(sorted_results) +
        "<div style='overflow-x:auto'><table style='width:100%%;border-collapse:collapse;border-radius:6px;overflow:hidden'>%s%s</table></div>" % (header, rows)
    )

    html = """<!DOCTYPE html>
<html><head><meta charset="utf-8"></head>
<body style="margin:0;padding:0;background:#0f172a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif">
<div style="max-width:900px;margin:0 auto;padding:24px 16px">
  <div style="margin-bottom:20px">
    <span style="color:#3b82f6;font-weight:700;font-size:18px;letter-spacing:1px">PEER</span><span style="color:#fff;font-weight:700;font-size:18px;letter-spacing:1px">CORTEX</span>
    <span style="color:#475569;font-size:12px;margin-left:10px">Daily Audit Report &middot; %(date)s</span>
  </div>
  <div style="background:%(sc)s18;border:1px solid %(sc)s40;border-radius:8px;padding:14px 18px;margin-bottom:20px">
    <div style="font-size:17px;font-weight:700;color:%(sc)s">%(emoji)s %(text)s</div>
    <div style="color:#94a3b8;font-size:12px;margin-top:4px">%(total)d ASNs tested from pool of %(pool)d &middot; Runtime: %(mins)dm %(secs)ds &middot; Cross-validated: RIPE Stat + PeeringDB</div>
  </div>
  <table style="width:100%%;border-collapse:collapse;margin-bottom:24px"><tr>
    <td style="width:22%%;text-align:center;background:#1e293b;border-radius:8px;padding:16px 8px">
      <div style="font-size:32px;font-weight:700;color:#22c55e">%(ok)d</div><div style="color:#64748b;font-size:10px;margin-top:4px">PERFECT</div></td>
    <td style="width:3%%"></td>
    <td style="width:22%%;text-align:center;background:#1e293b;border-radius:8px;padding:16px 8px">
      <div style="font-size:32px;font-weight:700;color:#f97316">%(warn)d</div><div style="color:#64748b;font-size:10px;margin-top:4px">WARNINGS</div></td>
    <td style="width:3%%"></td>
    <td style="width:22%%;text-align:center;background:#1e293b;border-radius:8px;padding:16px 8px">
      <div style="font-size:32px;font-weight:700;color:#ef4444">%(crit)d</div><div style="color:#64748b;font-size:10px;margin-top:4px">CRITICAL</div></td>
    <td style="width:3%%"></td>
    <td style="width:22%%;text-align:center;background:#1e293b;border-radius:8px;padding:16px 8px">
      <div style="font-size:32px;font-weight:700;color:#475569">%(skip)d</div><div style="color:#64748b;font-size:10px;margin-top:4px">SKIPPED</div></td>
  </tr></table>
  %(issues)s %(skipped)s %(rpki)s %(alltable)s
  <div style="margin-top:32px;padding-top:14px;border-top:1px solid #1e293b;color:#475569;font-size:11px">
    PeerCortex &middot; <a href="https://peercortex.org" style="color:#3b82f6;text-decoration:none">peercortex.org</a>
    &middot; 100 ASNs rotate daily from %(pool)d ASN pool &middot; Audit at 02:00 UTC, report at 06:00 UTC
  </div>
</div></body></html>""" % dict(
        date=date_str, sc=sc, emoji=emoji, text=text,
        total=total, pool=pool, mins=mins, secs=secs,
        ok=ok, warn=warn, crit=crit, skip=skip,
        issues=issues_html, skipped=skipped_html,
        rpki=rpki_html, alltable=all_table)

    return html, subject


def send_email(subject, html_body):
    msg = MIMEMultipart("alternative")
    msg["Subject"]    = subject
    msg["From"]       = MAIL_FROM
    msg["To"]         = MAIL_TO
    msg["Date"]       = formatdate(localtime=True)
    msg["Message-ID"] = make_msgid(domain="fichtmueller.org")
    msg.attach(MIMEText("PeerCortex Daily Audit — please view in an HTML-capable email client.\n\nhttps://peercortex.org", "plain", "utf-8"))
    msg.attach(MIMEText(html_body, "html", "utf-8"))
    with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=15) as s:
        s.sendmail(MAIL_FROM, [MAIL_TO], msg.as_bytes())
    print("Sent '%s' -> %s" % (subject, MAIL_TO))


if __name__ == "__main__":
    report, date_str = load_report()
    html, subject = build(report, date_str)
    print("Email size: %d bytes" % len(html.encode()))
    send_email(subject, html)
