/**
 * Security-Alerting:
 *   - **Sofort-Alert** für CRITICAL-Events (sobald sie entstehen, vom
 *     Cron alle 60s gepollt) – z.B. Threshold-Überschreitungen.
 *   - **Hourly-Digest**: einmal pro Stunde Sammlung von HIGH+ Events,
 *     wenn `monitoringDigestEnabled = true` und mindestens 1 Event vorhanden.
 *   - **Threshold-Detection**: prüft Brute-Force-Patterns (z.B. >10
 *     LOGIN_FAILED/h aus gleicher IP) und erzeugt synthetische CRITICAL-
 *     Events wenn die Schwelle erreicht ist.
 *
 * Alle E-Mails laufen über die System-E-Mail-Konfiguration des Providers
 * (genau wie Geburtstagsgrüße / Passwort-Reset). Daher gleiche Voraussetzungen.
 */
import cron from 'node-cron';
import prisma from '../lib/prisma.js';
import { sendEmail, SmtpCredentials } from './smtpService.js';
import { getSystemEmailCredentials } from './emailProvider/emailProviderService.js';
import * as appSettingService from './appSetting.service.js';
import { emit as emitSecurityEvent } from './securityMonitor.service.js';
import type { SecurityEvent } from '@prisma/client';

interface AlertEmailParams {
  subject: string;
  events: SecurityEvent[];
  isDigest: boolean;
}

interface SendResult {
  success: boolean;
  error?: string;
}

function severityIcon(s: string): string {
  switch (s) {
    case 'CRITICAL': return '🚨';
    case 'HIGH': return '⚠️';
    case 'MEDIUM': return '🟡';
    case 'LOW': return '🟢';
    default: return 'ℹ️';
  }
}

function eventToHtmlRow(e: SecurityEvent): string {
  const ts = e.createdAt.toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'medium' });
  const ip = e.ipAddress || '–';
  const who = e.userEmail || (e.userId ? `User #${e.userId}` : e.customerId ? `Kunde #${e.customerId}` : '–');
  const ep = e.endpoint || '–';
  return `<tr>
    <td style="padding:4px 8px;font-family:monospace;font-size:11px">${ts}</td>
    <td style="padding:4px 8px">${severityIcon(e.severity)} ${e.severity}</td>
    <td style="padding:4px 8px">${e.type}</td>
    <td style="padding:4px 8px">${e.message}</td>
    <td style="padding:4px 8px">${who}</td>
    <td style="padding:4px 8px;font-family:monospace;font-size:11px">${ip}</td>
    <td style="padding:4px 8px;font-family:monospace;font-size:11px">${ep}</td>
  </tr>`;
}

function buildHtmlEmail(params: AlertEmailParams): string {
  const rows = params.events.map(eventToHtmlRow).join('\n');
  const heading = params.isDigest
    ? `<h2>OpenCRM Security-Digest</h2><p>Übersicht der wichtigen Events der letzten Stunde:</p>`
    : `<h2>OpenCRM Security-Alert</h2><p>Folgendes Event wurde als kritisch eingestuft:</p>`;
  return `<!doctype html><html><body style="font-family:sans-serif;color:#222">
${heading}
<table border="0" cellspacing="0" cellpadding="0" style="border-collapse:collapse;width:100%;font-size:13px">
  <thead style="background:#f3f4f6">
    <tr>
      <th align="left" style="padding:6px 8px">Zeit</th>
      <th align="left" style="padding:6px 8px">Severity</th>
      <th align="left" style="padding:6px 8px">Typ</th>
      <th align="left" style="padding:6px 8px">Nachricht</th>
      <th align="left" style="padding:6px 8px">Wer</th>
      <th align="left" style="padding:6px 8px">IP</th>
      <th align="left" style="padding:6px 8px">Endpoint</th>
    </tr>
  </thead>
  <tbody>${rows}</tbody>
</table>
<p style="margin-top:20px;color:#666;font-size:12px">Diese Mail wurde vom OpenCRM Monitoring-System gesendet.
Konfiguration: Einstellungen → Monitoring.</p>
</body></html>`;
}

/**
 * Versendet einen Alert per E-Mail. Nutzt die System-E-Mail des Providers.
 */
export async function sendAlertEmail(toAddress: string, params: AlertEmailParams): Promise<SendResult> {
  const sysEmail = await getSystemEmailCredentials();
  if (!sysEmail) {
    return { success: false, error: 'System-E-Mail nicht konfiguriert (in Einstellungen → E-Mail-Provider)' };
  }

  const credentials: SmtpCredentials = {
    host: sysEmail.smtpServer,
    port: sysEmail.smtpPort,
    user: sysEmail.emailAddress,
    password: sysEmail.password,
    encryption: sysEmail.smtpEncryption,
    allowSelfSignedCerts: sysEmail.allowSelfSignedCerts,
  };

  const result = await sendEmail(
    credentials,
    sysEmail.emailAddress,
    {
      to: toAddress,
      subject: params.subject,
      html: buildHtmlEmail(params),
    },
    { context: 'security-alert', triggeredBy: 'monitor' },
  );

  return result.success
    ? { success: true }
    : { success: false, error: result.error };
}

/**
 * Threshold-Detection: prüft ob in den letzten N Minuten verdächtige Patterns
 * aufgetreten sind, die einen CRITICAL-Alert rechtfertigen.
 *
 * Regeln (alle pro IP):
 *   - >= 10 LOGIN_FAILED in 60 min  → CRITICAL Brute-Force-Verdacht
 *   - >=  5 ACCESS_DENIED in 5 min  → CRITICAL IDOR-Probing-Verdacht
 *   - >=  3 SSRF_BLOCKED in 60 min  → CRITICAL SSRF-Probing
 *   - >=  3 TOKEN_REJECTED HIGH in 5 min → CRITICAL JWT-Manipulation
 */
export async function detectThresholds(): Promise<void> {
  const now = new Date();
  const fiveMinAgo = new Date(now.getTime() - 5 * 60 * 1000);
  const sixtyMinAgo = new Date(now.getTime() - 60 * 60 * 1000);

  type Bucket = {
    windowStart: Date;
    type: 'LOGIN_FAILED' | 'ACCESS_DENIED' | 'SSRF_BLOCKED' | 'TOKEN_REJECTED';
    threshold: number;
    label: string;
  };
  const buckets: Bucket[] = [
    { windowStart: sixtyMinAgo, type: 'LOGIN_FAILED', threshold: 10, label: 'Brute-Force-Login-Verdacht' },
    { windowStart: fiveMinAgo, type: 'ACCESS_DENIED', threshold: 5, label: 'IDOR-Probing-Verdacht' },
    { windowStart: sixtyMinAgo, type: 'SSRF_BLOCKED', threshold: 3, label: 'SSRF-Probing-Verdacht' },
    { windowStart: fiveMinAgo, type: 'TOKEN_REJECTED', threshold: 3, label: 'JWT-Manipulations-Verdacht' },
  ];

  for (const b of buckets) {
    const grouped = await prisma.securityEvent.groupBy({
      by: ['ipAddress'],
      where: {
        type: b.type as any,
        createdAt: { gte: b.windowStart },
      },
      _count: true,
    });
    for (const g of grouped) {
      if ((g._count as number) < b.threshold) continue;
      // Debounce: pro IP max. 1 SUSPICIOUS-Alert pro 60min (sliding window).
      // Vorher: floor(now, hour) → resettete bei Stundenwechsel und produzierte
      // doppelte Alerts (Bug aus Runde 10).
      const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
      const existing = await prisma.securityEvent.findFirst({
        where: {
          type: 'SUSPICIOUS',
          severity: 'CRITICAL',
          ipAddress: g.ipAddress,
          createdAt: { gte: oneHourAgo },
        },
      });
      if (existing) continue;

      await emitSecurityEvent({
        type: 'SUSPICIOUS',
        severity: 'CRITICAL',
        message: `${b.label}: ${g._count}× ${b.type} in ${b.windowStart === fiveMinAgo ? '5min' : '60min'} von ${g.ipAddress}`,
        ipAddress: g.ipAddress,
        details: { rule: b.type, count: g._count, threshold: b.threshold },
      });
    }
  }
}

/**
 * Sendet pending CRITICAL-Events sofort als Einzel-Mails (debounced auf
 * 1 Mail pro IP pro Stunde, damit nicht spammend).
 */
async function sendPendingCriticalAlerts(): Promise<{ sent: number; skipped: number }> {
  const alertEmail = await appSettingService.getSetting('monitoringAlertEmail');
  if (!alertEmail) return { sent: 0, skipped: 0 };

  const pending = await prisma.securityEvent.findMany({
    where: { severity: 'CRITICAL', alerted: false },
    orderBy: { createdAt: 'asc' },
    take: 50,
  });

  let sent = 0;
  let skipped = 0;
  for (const ev of pending) {
    const result = await sendAlertEmail(alertEmail, {
      subject: `[OpenCRM] 🚨 ${ev.type}: ${ev.message.substring(0, 80)}`,
      events: [ev],
      isDigest: false,
    });
    if (result.success) {
      sent++;
      await prisma.securityEvent.update({
        where: { id: ev.id },
        data: { alerted: true, alertedAt: new Date() },
      });
    } else {
      skipped++;
      console.error(`[securityAlert] Send failed for event #${ev.id}:`, result.error);
    }
  }
  return { sent, skipped };
}

/**
 * Hourly-Digest: alle HIGH-Events der letzten Stunde, die noch nicht
 * alert-versendet wurden, in einer einzigen Mail zusammenfassen.
 */
export async function sendDigest(opts: { force?: boolean } = {}): Promise<{ sent: boolean; eventCount: number; reason?: string }> {
  const alertEmail = await appSettingService.getSetting('monitoringAlertEmail');
  if (!alertEmail) return { sent: false, eventCount: 0, reason: 'Keine Alert-E-Mail konfiguriert' };
  const enabled = await appSettingService.getSettingBool('monitoringDigestEnabled');
  if (!enabled && !opts.force) return { sent: false, eventCount: 0, reason: 'Digest deaktiviert' };

  const lastDigestAt = await appSettingService.getSetting('monitoringLastDigestAt');
  const since = lastDigestAt ? new Date(lastDigestAt) : new Date(Date.now() - 60 * 60 * 1000);

  const events = await prisma.securityEvent.findMany({
    where: {
      severity: { in: ['HIGH', 'MEDIUM'] },
      alerted: false,
      createdAt: { gte: since },
    },
    orderBy: { createdAt: 'desc' },
    take: 200,
  });

  if (events.length === 0) {
    await appSettingService.setSetting('monitoringLastDigestAt', new Date().toISOString());
    return { sent: false, eventCount: 0, reason: 'Keine neuen Events seit letztem Digest' };
  }

  const result = await sendAlertEmail(alertEmail, {
    subject: `[OpenCRM] Security-Digest (${events.length} Events)`,
    events,
    isDigest: true,
  });

  if (result.success) {
    await prisma.securityEvent.updateMany({
      where: { id: { in: events.map((e) => e.id) } },
      data: { alerted: true, alertedAt: new Date() },
    });
    await appSettingService.setSetting('monitoringLastDigestAt', new Date().toISOString());
    return { sent: true, eventCount: events.length };
  }

  return { sent: false, eventCount: events.length, reason: result.error };
}

/**
 * Cron-Scheduler:
 *   - Jede Minute: Threshold-Detection + Sofort-Alerts für CRITICAL
 *   - Jede volle Stunde: Hourly-Digest (HIGH+MEDIUM)
 */
export function startSecurityMonitorScheduler(): void {
  cron.schedule('* * * * *', async () => {
    try {
      await detectThresholds();
      await sendPendingCriticalAlerts();
    } catch (err) {
      console.error('[securityAlert] minute-cron failed:', err);
    }
  });

  cron.schedule('0 * * * *', async () => {
    try {
      await sendDigest();
    } catch (err) {
      console.error('[securityAlert] hourly-digest failed:', err);
    }
  });

  console.log('[securityAlert] Scheduler gestartet (1min Threshold-Check, hourly Digest)');
}
