"""
Email Service für SMTP-Versand und Template-Rendering
"""
import html
import json
import logging
import os
import re
import smtplib
from datetime import datetime
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from typing import Optional

from sqlalchemy.orm import Session
from app.models.setting import Setting

logger = logging.getLogger(__name__)

DEFAULT_SLA_BREACH_TEMPLATE = """<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: 'Segoe UI', Arial, sans-serif; margin: 0; padding: 20px; background: #fafafa; color: #333; }
        .container { max-width: 650px; margin: 0 auto; background: #fff; border-radius: 12px; border: 1px solid #e5e7eb; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); overflow: hidden; }
        .header { background: #7f1d1d; color: white; padding: 30px; text-align: center; }
        .header h1 { margin: 0; font-size: 24px; font-weight: 800; text-transform: uppercase; letter-spacing: 1px; }
        .body { padding: 40px; }
        .status-banner { background: #fef2f2; border: 1px solid #fee2e2; border-radius: 8px; padding: 15px; margin-bottom: 30px; text-align: center; }
        .status-text { color: #991b1b; font-weight: 700; font-size: 18px; margin: 0; }
        
        .info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px; }
        .info-card { background: #f9fafb; padding: 15px; border-radius: 8px; border: 1px solid #f3f4f6; }
        .info-label { display: block; font-size: 11px; font-weight: 700; color: #6b7280; text-transform: uppercase; margin-bottom: 5px; }
        .info-value { font-size: 16px; font-weight: 600; color: #111827; }
        
        .severity-badge { display: inline-block; padding: 4px 12px; rounded: 100px; font-size: 14px; font-weight: 700; border-radius: 20px; }
        .severity-critical { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
        .severity-high { background: #ffedd5; color: #9a3412; border: 1px solid #fed7aa; }
        .severity-medium { background: #dbeafe; color: #1e40af; border: 1px solid #bfdbfe; }
        .severity-low { background: #dcfce7; color: #166534; border: 1px solid #bbf7d0; }

        .btn { display: block; text-align: center; background: #111827; color: #ffffff !important; padding: 16px; border-radius: 8px; text-decoration: none; font-weight: 700; margin-top: 30px; font-size: 16px; letter-spacing: 0.5px; }
        .footer { padding: 25px; background: #f9fafb; border-top: 1px solid #e5e7eb; font-size: 12px; color: #6b7280; text-align: center; line-height: 1.6; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>SLA Violation Warning</h1>
        </div>
        <div class="body">
            <div class="status-banner">
                <p class="status-text">CRITICAL: SLA Breach by {{hours_overdue}} Hours</p>
                <p style="margin: 5px 0 0 0; color: #b91c1c; font-size: 14px;">Immediate action required for compliance.</p>
            </div>

            <div class="info-grid">
                <div class="info-card">
                    <span class="info-label">CVE Identifier</span>
                    <span class="info-value" style="color: #7f1d1d;">{{cve_id}}</span>
                </div>
                <div class="info-card">
                    <span class="info-label">SLA Severity</span>
                    <span class="severity-badge severity-{{severity}}">{{severity_upper}}</span>
                </div>
                <div class="info-card" style="grid-column: span 2;">
                    <span class="info-label">Affected Host / System</span>
                    <span class="info-value">{{asset_hostname}}</span>
                </div>
                <div class="info-card">
                    <span class="info-label">CVSS Score</span>
                    <span class="info-value text-red-600">{{cvss_score}}</span>
                </div>
                <div class="info-card">
                    <span class="info-label">Assigned To</span>
                    <span class="info-value">{{assigned_user}}</span>
                </div>
                <div class="info-card" style="grid-column: span 2;">
                    <span class="info-label">Resource / Package</span>
                    <span class="info-value">{{package_name}}</span>
                </div>
            </div>

            <p style="color: #4b5563; font-size: 14px; line-height: 1.6; background: #fffbeb; border-left: 4px solid #f59e0b; padding: 12px; border-radius: 4px;">
                <strong>Summary:</strong> {{title}}
            </p>
            
            <p style="color: #4b5563; font-size: 14px; line-height: 1.6; margin-top: 20px;">
                This vulnerability was detected at {{detected_at}} and has exceeded its mandatory remediation window. 
                Please remediate this exposure immediately or provide a valid deferral reason in the management console.
            </p>

            <a href="{{dashboard_url}}" class="btn">REMEDIATE NOW</a>
        </div>
        <div class="footer">
            <strong>VulnCheck Security Operations Center</strong><br>
            Automated compliance monitoring system. Do not reply to this email.<br>
            <span style="font-size: 10px; opacity: 0.7;">Sent to: {{recipient_name}} ({{recipient_email}})</span>
        </div>
    </div>
</body>
</html>"""

DEFAULT_SLA_BREACH_SUBJECT = "SLA Breach: {{cve_id}} on {{asset_hostname}} ({{severity_upper}})"

DEFAULT_NEW_VULN_TEMPLATE = """<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px; background: #fdfbf7; color: #333; }
        .container { max-width: 600px; margin: 0 auto; background: #fff; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); overflow: hidden; }
        .header { background: #d9480f; color: white; padding: 25px; text-align: center; }
        .header h1 { margin: 0; font-size: 24px; font-weight: 600; letter-spacing: 0.5px; }
        .body { padding: 30px; }
        .alert-badge { display: inline-block; background: #fff5f5; color: #c92a2a; border: 1px solid #ffc9c9; padding: 6px 12px; border-radius: 20px; font-weight: bold; font-size: 14px; margin-bottom: 20px; }
        .detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 20px; }
        .detail-item { background: #f8f9fa; padding: 15px; border-radius: 6px; }
        .detail-label { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #868e96; margin-bottom: 5px; }
        .detail-value { font-weight: 600; font-size: 15px; color: #212529; }
        .description-box { background: #fff; border: 1px solid #e9ecef; padding: 15px; border-radius: 6px; margin-bottom: 20px; line-height: 1.5; font-size: 14px; color: #495057; }
        .action-btn { display: block; width: 100%; text-align: center; background: #339af0; color: white; padding: 15px 0; border-radius: 6px; text-decoration: none; font-weight: bold; font-size: 16px; transition: background 0.2s; }
        .action-btn:hover { background: #228be6; }
        .footer { padding: 20px; background: #f8f9fa; text-align: center; font-size: 12px; color: #adb5bd; border-top: 1px solid #e9ecef; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>New Vulnerability Detected</h1>
        </div>
        <div class="body">
            <div style="text-align: center;">
                <span class="alert-badge">Severity: {{severity_upper}} ({{cvss_score}})</span>
            </div>
            
            <p style="font-size: 16px; margin-bottom: 25px; text-align: center;">
                A new <strong>{{severity_upper}}</strong> vulnerability has been detected on <strong>{{asset_hostname}}</strong>.
            </p>

            <div class="detail-grid">
                <div class="detail-item">
                    <span class="detail-label">CVE ID</span>
                    <span class="detail-value" style="color: #d9480f;">{{cve_id}}</span>
                </div>
                <div class="detail-item">
                    <span class="detail-label">Package</span>
                    <span class="detail-value">{{package_name}}</span>
                </div>
                <div class="detail-item">
                    <span class="detail-label">Detected At</span>
                    <span class="detail-value">{{detected_at}}</span>
                </div>
                <div class="detail-item">
                    <span class="detail-label">Affected Host</span>
                    <span class="detail-value">{{asset_hostname}}</span>
                </div>
            </div>

            <div class="detail-label">Description</div>
            <div class="description-box">
                {{description}}
            </div>

            <a href="{{dashboard_url}}" class="action-btn">View Details & Remediate</a>
        </div>
        <div class="footer">
            Generated by VulnCheck Dashboard • {{detected_at}}
        </div>
    </div>
</body>
</html>"""

DEFAULT_NEW_VULN_SUBJECT = "ALERT: New {{severity_upper}} Vulnerability ({{cve_id}}) on {{asset_hostname}}"


# ---------------------------------------------------------------
# Digest variant — one mail per recipient summarising N new CVEs
# instead of one mail per CVE. Selected by setting `notification_mode`.
# ---------------------------------------------------------------
DEFAULT_DIGEST_SUBJECT = "[VULNCHECK] {{total}} new vulnerabilities detected"
DEFAULT_DIGEST_TEMPLATE = """<!DOCTYPE html>
<html><head><meta charset="utf-8"><style>
body{font-family:Arial,sans-serif;color:#1f2937;max-width:760px;margin:24px auto;padding:0 16px}
.h{background:#1f2937;color:#fff;padding:14px 18px;border-radius:6px 6px 0 0}
.h h1{margin:0;font-size:18px}
.h .sub{font-size:12px;opacity:.85;margin-top:4px}
.box{background:#fff;border:1px solid #e5e7eb;border-top:none;padding:18px;border-radius:0 0 6px 6px}
.cnt{display:flex;gap:12px;flex-wrap:wrap;margin:0 0 14px 0}
.cnt div{flex:1;min-width:90px;text-align:center;padding:8px;border-radius:4px;font-size:12px;font-weight:bold}
.crit{background:#fee2e2;color:#991b1b}.high{background:#ffedd5;color:#9a3412}
.med{background:#dbeafe;color:#1e40af}.low{background:#dcfce7;color:#166534}
table{width:100%;border-collapse:collapse;margin-top:8px;font-size:13px}
th{text-align:left;background:#f3f4f6;padding:8px 6px;border-bottom:1px solid #e5e7eb;font-size:11px;text-transform:uppercase;color:#6b7280}
td{padding:7px 6px;border-bottom:1px solid #f3f4f6}
.sev{display:inline-block;padding:1px 6px;border-radius:3px;font-size:10px;font-weight:bold;text-transform:uppercase}
.sev-critical{background:#fee2e2;color:#991b1b}.sev-high{background:#ffedd5;color:#9a3412}
.sev-medium{background:#dbeafe;color:#1e40af}.sev-low{background:#dcfce7;color:#166534}
.btn{display:inline-block;margin-top:14px;padding:9px 16px;background:#2563eb;color:#fff;text-decoration:none;border-radius:4px;font-size:13px}
.foot{font-size:11px;color:#6b7280;margin-top:14px;padding-top:10px;border-top:1px solid #e5e7eb}
</style></head><body>
<div class="h">
  <h1>{{total}} new vulnerabilities detected</h1>
  <div class="sub">Sync at {{detected_at}} — recipient: {{recipient_name}}</div>
</div>
<div class="box">
  <div class="cnt">
    <div class="crit">CRITICAL<br>{{count_critical}}</div>
    <div class="high">HIGH<br>{{count_high}}</div>
    <div class="med">MEDIUM<br>{{count_medium}}</div>
    <div class="low">LOW<br>{{count_low}}</div>
  </div>
  <table>
    <thead><tr><th>CVE</th><th>Severity</th><th>CVSS</th><th>Host</th><th>Package</th></tr></thead>
    <tbody>{{rows}}</tbody>
  </table>
  <a class="btn" href="{{dashboard_url}}">Open in dashboard</a>
  <div class="foot">
    You receive this because the affected asset or vulnerability is assigned to you or one of your groups.
    Manage assignments and suppression in the VulnCheck UI.
  </div>
</div></body></html>"""


def render_digest_rows(items: list) -> str:
    """Render the <tr> rows for the digest table. Items: list of dicts with
    cve_id, severity, cvss_score, asset_hostname, package_name. All dynamic
    values HTML-escaped to prevent injection from compromised scanner data."""
    rows = []
    for it in items:
        raw_sev = (it.get("severity") or "none").lower()
        # whitelist severity for CSS class — anything else falls back to 'none'
        sev = raw_sev if raw_sev in {"critical", "high", "medium", "low", "none"} else "none"
        cvss = it.get("cvss_score")
        cvss_str = html.escape(str(cvss)) if cvss is not None else "-"
        rows.append(
            f"<tr>"
            f"<td><strong>{html.escape(str(it.get('cve_id', '')))}</strong></td>"
            f"<td><span class='sev sev-{sev}'>{sev.upper()}</span></td>"
            f"<td>{cvss_str}</td>"
            f"<td>{html.escape(str(it.get('asset_hostname', '')))}</td>"
            f"<td>{html.escape(str(it.get('package_name') or '')[:60])}</td>"
            f"</tr>"
        )
    return "".join(rows)


DEFAULT_SLA_DIGEST_SUBJECT = "[VULNCHECK] {{total}} SLA-breached vulnerabilities require action"
DEFAULT_SLA_DIGEST_TEMPLATE = """<!DOCTYPE html>
<html><head><meta charset="utf-8"><style>
body{font-family:Arial,sans-serif;color:#1f2937;max-width:780px;margin:24px auto;padding:0 16px}
.h{background:#991b1b;color:#fff;padding:14px 18px;border-radius:6px 6px 0 0}
.h h1{margin:0;font-size:18px}
.h .sub{font-size:12px;opacity:.85;margin-top:4px}
.box{background:#fff;border:1px solid #e5e7eb;border-top:none;padding:18px;border-radius:0 0 6px 6px}
.warn{background:#fef2f2;border-left:4px solid #dc2626;color:#7f1d1d;padding:10px 12px;margin-bottom:12px;font-size:13px;border-radius:4px}
.cnt{display:flex;gap:12px;flex-wrap:wrap;margin:0 0 14px 0}
.cnt div{flex:1;min-width:90px;text-align:center;padding:8px;border-radius:4px;font-size:12px;font-weight:bold}
.crit{background:#fee2e2;color:#991b1b}.high{background:#ffedd5;color:#9a3412}
.med{background:#dbeafe;color:#1e40af}.low{background:#dcfce7;color:#166534}
table{width:100%;border-collapse:collapse;margin-top:8px;font-size:13px}
th{text-align:left;background:#f3f4f6;padding:8px 6px;border-bottom:1px solid #e5e7eb;font-size:11px;text-transform:uppercase;color:#6b7280}
td{padding:7px 6px;border-bottom:1px solid #f3f4f6;vertical-align:top}
.sev{display:inline-block;padding:1px 6px;border-radius:3px;font-size:10px;font-weight:bold;text-transform:uppercase}
.sev-critical{background:#fee2e2;color:#991b1b}.sev-high{background:#ffedd5;color:#9a3412}
.sev-medium{background:#dbeafe;color:#1e40af}.sev-low{background:#dcfce7;color:#166534}
.ovd{color:#991b1b;font-weight:bold}
.btn{display:inline-block;margin-top:14px;padding:9px 16px;background:#dc2626;color:#fff;text-decoration:none;border-radius:4px;font-size:13px}
.foot{font-size:11px;color:#6b7280;margin-top:14px;padding-top:10px;border-top:1px solid #e5e7eb}
</style></head><body>
<div class="h">
  <h1>{{total}} SLA-breached vulnerabilities require action</h1>
  <div class="sub">Check at {{checked_at}} — recipient: {{recipient_name}}</div>
</div>
<div class="box">
  <div class="warn"><strong>Action required.</strong> These findings exceeded their remediation SLA. Please patch or formally accept the risk.</div>
  <div class="cnt">
    <div class="crit">CRITICAL<br>{{count_critical}}</div>
    <div class="high">HIGH<br>{{count_high}}</div>
    <div class="med">MEDIUM<br>{{count_medium}}</div>
    <div class="low">LOW<br>{{count_low}}</div>
  </div>
  <table>
    <thead><tr><th>CVE</th><th>Severity</th><th>Host</th><th>Detected</th><th>Overdue</th></tr></thead>
    <tbody>{{rows}}</tbody>
  </table>
  <a class="btn" href="{{dashboard_url}}">Open in dashboard</a>
  <div class="foot">
    You receive this because these vulnerabilities or their affected assets are assigned to you or one of your groups.
    To stop receiving alerts for a specific finding, suppress notifications via the bell icon in the dashboard or
    mark the vulnerability as a false positive.
  </div>
</div></body></html>"""


def render_sla_digest_rows(items: list) -> str:
    """Render <tr> rows for the SLA digest table. Items: list of dicts with
    cve_id, severity, asset_hostname, detected_at, hours_overdue. All
    dynamic values HTML-escaped to prevent injection."""
    rows = []
    for it in items:
        raw_sev = (it.get("severity") or "none").lower()
        sev = raw_sev if raw_sev in {"critical", "high", "medium", "low", "none"} else "none"
        hours = int(it.get("hours_overdue") or 0)
        if hours >= 48:
            overdue_str = f"{hours // 24}d {hours % 24}h"
        else:
            overdue_str = f"{hours}h"
        rows.append(
            f"<tr>"
            f"<td><strong>{html.escape(str(it.get('cve_id', '')))}</strong></td>"
            f"<td><span class='sev sev-{sev}'>{sev.upper()}</span></td>"
            f"<td>{html.escape(str(it.get('asset_hostname', '')))}</td>"
            f"<td>{html.escape(str(it.get('detected_at', '')))}</td>"
            f"<td class='ovd'>{overdue_str}</td>"
            f"</tr>"
        )
    return "".join(rows)


def send_sla_breach_digest(
    db: Session,
    to_email: str,
    recipient_name: str,
    items: list,
    checked_at: str,
    dashboard_url: str,
) -> tuple[bool, str]:
    """One SLA-breach digest mail aggregating all overdue findings for a recipient.
    Replaces per-vuln SLA-breach emails when notification_mode='digest'."""
    if not items:
        return False, "no items"

    counts = {"critical": 0, "high": 0, "medium": 0, "low": 0, "none": 0}
    for it in items:
        sev = (it.get("severity") or "none").lower()
        if sev in counts:
            counts[sev] += 1

    variables = {
        "total": str(len(items)),
        "count_critical": str(counts["critical"]),
        "count_high": str(counts["high"]),
        "count_medium": str(counts["medium"]),
        "count_low": str(counts["low"]),
        "checked_at": checked_at,
        "recipient_name": recipient_name,
        "recipient_email": to_email,
        "dashboard_url": dashboard_url,
        "rows": render_sla_digest_rows(items),
    }

    subject_template, body_template = get_email_template(db, "email_template_sla_breach_digest")
    subject = render_template(subject_template, variables)
    body = render_template(body_template, variables)
    return send_email(db, to_email, subject, body)


def send_new_vulnerability_digest(
    db: Session,
    to_email: str,
    recipient_name: str,
    items: list,
    detected_at: str,
    dashboard_url: str,
) -> tuple[bool, str]:
    """
    Send one digest email aggregating up to N new vulnerabilities for a recipient.
    Used by the Wazuh sync when notification_mode = 'digest' (the default).
    """
    if not items:
        return False, "no items"

    counts = {"critical": 0, "high": 0, "medium": 0, "low": 0, "none": 0}
    for it in items:
        sev = (it.get("severity") or "none").lower()
        if sev in counts:
            counts[sev] += 1

    variables = {
        "total": str(len(items)),
        "count_critical": str(counts["critical"]),
        "count_high": str(counts["high"]),
        "count_medium": str(counts["medium"]),
        "count_low": str(counts["low"]),
        "detected_at": detected_at,
        "recipient_name": recipient_name,
        "recipient_email": to_email,
        "dashboard_url": dashboard_url,
        "rows": render_digest_rows(items),
    }

    subject_template, body_template = get_email_template(db, "email_template_new_vuln_digest")
    subject = render_template(subject_template, variables)
    body = render_template(body_template, variables)
    return send_email(db, to_email, subject, body)


def get_smtp_config(db: Session) -> Optional[dict]:
    from app.auth.setting_crypto import read_setting_value
    raw = read_setting_value(db, "smtp_config")
    if not raw:
        return None
    try:
        return json.loads(raw)
    except (json.JSONDecodeError, TypeError):
        return None


def get_email_template(db: Session, template_key: str = "email_template_sla_breach") -> tuple[str, str]:
    setting = db.query(Setting).filter(Setting.key == template_key).first()
    
    default_subject = DEFAULT_SLA_BREACH_SUBJECT
    default_body = DEFAULT_SLA_BREACH_TEMPLATE
    
    if template_key == "email_template_new_vuln":
        default_subject = DEFAULT_NEW_VULN_SUBJECT
        default_body = DEFAULT_NEW_VULN_TEMPLATE
    elif template_key == "email_template_new_vuln_digest":
        default_subject = DEFAULT_DIGEST_SUBJECT
        default_body = DEFAULT_DIGEST_TEMPLATE
    elif template_key == "email_template_sla_breach_digest":
        default_subject = DEFAULT_SLA_DIGEST_SUBJECT
        default_body = DEFAULT_SLA_DIGEST_TEMPLATE

    if setting and setting.value:
        try:
            data = json.loads(setting.value)
            return data.get("subject", default_subject), data.get("body", default_body)
        except (json.JSONDecodeError, TypeError):
            pass
    return default_subject, default_body


# Keys whose values are already trusted HTML (pre-rendered with escaping).
# Everything else is HTML-escaped at substitution time.
_SAFE_HTML_KEYS = {"rows"}


def render_template(template: str, variables: dict) -> str:
    def replace_var(match):
        key = match.group(1)
        if key not in variables:
            return f"{{{{{key}}}}}"
        value = str(variables[key])
        if key in _SAFE_HTML_KEYS:
            return value
        return html.escape(value)
    return re.sub(r'\{\{(\w+)\}\}', replace_var, template)


def send_email(db: Session, to_email: str, subject: str, html_body: str, config_override: Optional[dict] = None) -> tuple[bool, str]:
    if config_override:
        config = config_override
    else:
        config = get_smtp_config(db)
        
    if not config:
        return False, "SMTP not configured"

    host = config.get("host", "")
    port = int(config.get("port", 587))
    username = config.get("username", "")
    password = config.get("password", "")
    from_address = config.get("from_address", username)
    use_tls = config.get("use_tls", True)

    if not host:
        return False, "SMTP host not configured"

    try:
        msg = MIMEMultipart("alternative")
        msg["From"] = from_address
        msg["To"] = to_email
        msg["Subject"] = subject
        msg.attach(MIMEText(html_body, "html"))

        if use_tls:
            server = smtplib.SMTP(host, port, timeout=10)
            server.starttls()
        else:
            server = smtplib.SMTP(host, port, timeout=10)

        if username and password:
            server.login(username, password)

        server.sendmail(from_address, [to_email], msg.as_string())
        server.quit()
        logger.info(f"Email sent to {to_email}: {subject}")
        return True, "OK"

    except smtplib.SMTPAuthenticationError as e:
        msg = f"SMTP authentication failed: {e}"
        logger.error(msg)
        return False, msg
    except smtplib.SMTPException as e:
        msg = f"SMTP error: {e}"
        logger.error(msg)
        return False, msg
    except Exception as e:
        msg = f"Email send failed: {e}"
        logger.error(msg)
        return False, msg


def send_sla_breach_notification(db: Session, to_email: str, variables: dict) -> tuple[bool, str]:
    subject_template, body_template = get_email_template(db, "email_template_sla_breach")
    subject = render_template(subject_template, variables)
    body = render_template(body_template, variables)
    return send_email(db, to_email, subject, body)


def send_new_vulnerability_notification(db: Session, to_email: str, variables: dict) -> tuple[bool, str]:
    subject_template, body_template = get_email_template(db, "email_template_new_vuln")
    subject = render_template(subject_template, variables)
    body = render_template(body_template, variables)
    return send_email(db, to_email, subject, body)


# Severity rank: higher = more severe.
_SEVERITY_RANK = {
    "none": 0,
    "low": 1,
    "medium": 2,
    "high": 3,
    "critical": 4,
}


def get_notification_mode(db: Session) -> str:
    """
    `notification_mode` setting: 'digest' (default) or 'single'.
    digest = one summary email per recipient per sync run.
    single = one email per CVE per recipient (legacy, SMTP-spammy).
    """
    from app.models.setting import Setting
    try:
        s = db.query(Setting).filter(Setting.key == "notification_mode").first()
        if s and s.value:
            val = s.value.strip().lower()
            if val in ("digest", "single"):
                return val
    except Exception:
        pass
    return "digest"


def _resolve_recipients_for_vuln(db: Session, vuln) -> list[tuple[int, str, str]]:
    """
    Returns list of (user_id, email, username) for a vulnerability following
    the cascade: vuln.assigned_user > vuln.assigned_group > asset.assigned_user
    > asset.assigned_group. Empty list when nothing matches.
    """
    from app.models.group import Group
    from app.models.user import User

    recipients: list[tuple[int, str, str]] = []
    seen_emails: set[str] = set()

    def _push(u):
        if u and u.email and u.email not in seen_emails:
            recipients.append((u.id, u.email, u.username))
            seen_emails.add(u.email)

    if vuln.assigned_user_id:
        _push(db.query(User).filter(User.id == vuln.assigned_user_id).first())
    elif vuln.assigned_group_id:
        g = db.query(Group).filter(Group.id == vuln.assigned_group_id).first()
        if g:
            for u in g.users:
                _push(u)
    elif vuln.asset and vuln.asset.assigned_user_id:
        _push(db.query(User).filter(User.id == vuln.asset.assigned_user_id).first())
    elif vuln.asset and getattr(vuln.asset, "groups", None):
        for g in vuln.asset.groups:
            for u in g.users:
                _push(u)
    return recipients


def dispatch_new_vuln_notifications(db: Session, new_vulns: list) -> dict:
    """
    Entry point for the Wazuh sync. Groups new vulns by recipient,
    applies the severity threshold, and dispatches either:
      - one digest email per recipient (mode='digest', default), or
      - one email per CVE per recipient (mode='single', legacy).

    Returns a stats dict for logging.
    """
    from app.models.notification_log import NotificationLog, NotificationType, NotificationStatus

    stats = {"emails_sent": 0, "emails_failed": 0, "recipients": 0, "vulns_considered": len(new_vulns)}
    if not new_vulns:
        return stats

    smtp = get_smtp_config(db)
    if not smtp:
        logger.info("SMTP not configured — skipping new-vuln notifications")
        return stats

    mode = get_notification_mode(db)
    dashboard_url = os.getenv("DASHBOARD_URL", "http://localhost:3000").rstrip("/") + "/vulnerabilities"
    detected_at_str = datetime.now().strftime("%Y-%m-%d %H:%M UTC")

    # Bucket vulns per recipient email (only those above the severity threshold).
    buckets: dict[str, dict] = {}  # email -> {user_id, username, items: [vuln_summary]}
    notif_log_anchors: dict[str, list] = {}  # email -> list of (vuln_id, asset_id) for logging

    for vuln in new_vulns:
        if not should_notify_for_severity(db, vuln.severity):
            continue
        for r_uid, r_email, r_username in _resolve_recipients_for_vuln(db, vuln):
            b = buckets.setdefault(r_email, {"user_id": r_uid, "username": r_username, "items": []})
            b["items"].append({
                "cve_id": vuln.cve_id,
                "severity": vuln.severity.value if vuln.severity else "none",
                "cvss_score": vuln.cvss_score,
                "asset_hostname": vuln.asset.hostname if vuln.asset else "Unknown",
                "package_name": vuln.package_name,
                "vuln_id": vuln.id,
                "asset_id": vuln.asset_id,
            })
            notif_log_anchors.setdefault(r_email, []).append((vuln.id, vuln.asset_id))

    stats["recipients"] = len(buckets)

    for email, bucket in buckets.items():
        items = bucket["items"]
        username = bucket["username"]
        user_id = bucket["user_id"]

        if mode == "digest":
            success, err = send_new_vulnerability_digest(
                db,
                to_email=email,
                recipient_name=username,
                items=items,
                detected_at=detected_at_str,
                dashboard_url=dashboard_url,
            )
            anchor_vuln_id = items[0]["vuln_id"]
            anchor_asset_id = items[0]["asset_id"]
            db.add(NotificationLog(
                vulnerability_id=anchor_vuln_id,  # anchor — full list is in body
                asset_id=anchor_asset_id,
                user_id=user_id,
                notification_type=NotificationType.NEW_VULNERABILITY,
                sent_at=datetime.now(),
                subject=f"[VULNCHECK] {len(items)} new vulnerabilities detected",
                recipient_email=email,
                status=NotificationStatus.SENT if success else NotificationStatus.FAILED,
                message_body=f"Digest of {len(items)} new vulnerabilities",
                error_message=None if success else err,
            ))
            if success:
                stats["emails_sent"] += 1
                logger.info(f"Digest email sent to {email}: {len(items)} CVEs")
            else:
                stats["emails_failed"] += 1
                logger.warning(f"Digest email FAILED to {email}: {err}")
        else:
            # Legacy single mode — one mail per CVE per recipient
            for item in items:
                variables = {
                    "cve_id": item["cve_id"],
                    "severity": item["severity"],
                    "severity_upper": item["severity"].upper(),
                    "cvss_score": str(item.get("cvss_score") or "N/A"),
                    "asset_hostname": item["asset_hostname"],
                    "package_name": item.get("package_name") or "",
                    "title": item["cve_id"],
                    "detected_at": detected_at_str,
                    "dashboard_url": dashboard_url,
                    "recipient_name": username,
                    "recipient_email": email,
                }
                success, err = send_new_vulnerability_notification(db, email, variables)
                db.add(NotificationLog(
                    vulnerability_id=item["vuln_id"],
                    asset_id=item["asset_id"],
                    user_id=user_id,
                    notification_type=NotificationType.NEW_VULNERABILITY,
                    sent_at=datetime.now(),
                    subject=f"[VULNCHECK] New {item['severity'].upper()} Vulnerability: {item['cve_id']}",
                    recipient_email=email,
                    status=NotificationStatus.SENT if success else NotificationStatus.FAILED,
                    message_body=f"New {item['severity']} vulnerability {item['cve_id']} on {item['asset_hostname']}",
                    error_message=None if success else err,
                ))
                if success:
                    stats["emails_sent"] += 1
                else:
                    stats["emails_failed"] += 1

    db.commit()
    return stats


def should_notify_for_severity(db: Session, severity) -> bool:
    """
    Return True if the configured notification threshold lets this severity
    trigger a 'new vulnerability' email.

    Threshold is stored in setting `notification_min_severity`
    (one of: critical, high, medium, low). Default: critical.
    A severity at or above the threshold triggers a notification.
    """
    from app.models.setting import Setting

    threshold = "critical"
    try:
        setting = db.query(Setting).filter(Setting.key == "notification_min_severity").first()
        if setting and setting.value:
            candidate = setting.value.strip().lower()
            if candidate in _SEVERITY_RANK:
                threshold = candidate
    except Exception as e:
        logger.warning(f"Failed to read notification_min_severity setting, defaulting to 'critical': {e}")

    severity_value = getattr(severity, "value", severity)
    if not isinstance(severity_value, str):
        return False
    return _SEVERITY_RANK.get(severity_value.lower(), -1) >= _SEVERITY_RANK[threshold]
