import prisma from '../lib/prisma.js';
import { emit as emitSecurityEvent } from './securityMonitor.service.js';

/**
 * Wachhund auf den AUSBLEIBENDEN Heartbeat (Pentest R182/R183).
 *
 * Dienstkonten – allen voran das Gegenbuch – melden sich in festem Takt an.
 * Diese Anmeldungen sind bewusst als Routine eingestuft, damit sie die
 * CRITICAL-Stufe nicht entwerten. Damit fehlt aber die andere Haelfte: Wer das
 * Gegenbuch stilllegt, setzt genau darauf, dass STILLE nicht auffaellt. Ein
 * gestopptes Gegenbuch ist sonst nicht von „laeuft ruhig“ zu unterscheiden.
 *
 * Das Sicherheitssignal ist deshalb nicht die Anwesenheit der Anmeldung,
 * sondern ihre Abweichung: bleibt sie aus, wird es gemeldet.
 *
 * Bewusste Zurueckhaltung: Ohne jemals gesehene Anmeldung gibt es keine
 * Grundlinie – dann wird geschwiegen statt geraten. Und pro Ausfall wird nur
 * einmal gemeldet, nicht bei jedem Durchlauf.
 */
const PRUEFTAKT_MS = 15 * 60 * 1000;
const MAX_ALTER_MINUTEN = Number.parseInt(process.env.SERVICE_ACCOUNT_MAX_SILENCE_MINUTES || '180', 10);

export async function pruefeHeartbeats(): Promise<void> {
  const konten = await prisma.user.findMany({
    where: { isServiceAccount: true, isActive: true },
    select: { id: true, email: true },
  });
  if (konten.length === 0) return;

  const grenze = new Date(Date.now() - MAX_ALTER_MINUTEN * 60 * 1000);

  for (const konto of konten) {
    const letzte = await prisma.auditLog.findFirst({
      where: { userId: konto.id, action: 'LOGIN' },
      orderBy: { createdAt: 'desc' },
      select: { createdAt: true },
    });

    // Nie angemeldet = keine Grundlinie. Schweigen statt raten.
    if (!letzte) continue;
    if (letzte.createdAt >= grenze) continue;

    // Pro Ausfall nur einmal melden.
    const schonGemeldet = await prisma.securityEvent.findFirst({
      where: {
        type: 'SUSPICIOUS',
        userEmail: konto.email,
        createdAt: { gte: letzte.createdAt },
        message: { contains: 'Dienstkonto' },
      },
    });
    if (schonGemeldet) continue;

    const stillSeit = Math.round((Date.now() - letzte.createdAt.getTime()) / 60000);
    await emitSecurityEvent({
      type: 'SUSPICIOUS',
      severity: 'CRITICAL',
      message:
        `Dienstkonto ${konto.email} meldet sich seit ${stillSeit} Minuten nicht mehr ` +
        `(erwartet mindestens alle ${MAX_ALTER_MINUTEN} Minuten). ` +
        'Entweder steht der zugehörige Dienst – etwa das Gegenbuch – oder er wurde stillgelegt. ' +
        'Stille ist hier kein guter Zustand: Ohne laufendes Gegenbuch fällt eine nachträgliche ' +
        'Änderung am Audit-Log nicht mehr auf.',
      userEmail: konto.email,
      userId: konto.id,
      endpoint: 'heartbeat-monitor',
    });
  }
}

export function starteHeartbeatMonitor(): void {
  if (!Number.isFinite(MAX_ALTER_MINUTEN) || MAX_ALTER_MINUTEN <= 0) {
    console.warn('[Heartbeat] SERVICE_ACCOUNT_MAX_SILENCE_MINUTES ungültig – Wachhund bleibt aus.');
    return;
  }
  const lauf = () => {
    pruefeHeartbeats().catch((e) => console.error('[Heartbeat] Fehler:', e));
  };
  // Nicht sofort beim Start: Nach einem Neustart darf ein kurz zurueckliegender
  // Ausfall nicht doppelt melden, und die DB soll erst oben sein.
  setTimeout(lauf, 60_000);
  setInterval(lauf, PRUEFTAKT_MS).unref();
}
