/**
 * Idempotenter Permissions+Rollen-Sync für den Container-Start.
 *
 * Hintergrund: seed.ts läuft nur auf leeren DBs (USER_COUNT=0). Wer das
 * System schon installiert hat, bekommt nachträglich hinzugefügte
 * Permissions oder neue Rollenzuordnungen NICHT — die DSGVO-Rolle kann
 * dann z.B. ohne audit:read landen, obwohl Settings.tsx das voraussetzt.
 *
 * Dieses Skript synchronisiert ausschließlich:
 *  - Permission-Katalog (resource/action-Paare aus dem Code)
 *  - Roll-Zuordnungen (Admin, Developer, DSGVO, Mitarbeiter,
 *    Mitarbeiter (Nur-Lesen), Kunde)
 *
 * KEINE Stammdaten, KEINE User, KEINE Verträge — das Skript ist auf
 * laufenden Prod-DBs sicher.
 */

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

const RESOURCE_PERMISSIONS: Record<string, string[]> = {
  customers: ['create', 'read', 'update', 'delete'],
  contracts: ['create', 'read', 'update', 'delete'],
  users: ['create', 'read', 'update', 'delete'],
  platforms: ['create', 'read', 'update', 'delete'],
  providers: ['create', 'read', 'update', 'delete'],
  tariffs: ['create', 'read', 'update', 'delete'],
  'cancellation-periods': ['create', 'read', 'update', 'delete'],
  'contract-durations': ['create', 'read', 'update', 'delete'],
  'contract-categories': ['create', 'read', 'update', 'delete'],
  'email-providers': ['create', 'read', 'update', 'delete'],
  settings: ['read', 'update'],
  developer: ['access'],
  emails: ['delete'],
  audit: ['read', 'export', 'admin'],
  gdpr: ['export', 'delete', 'admin'],
};

async function syncRolePermissions(roleId: number, permissionIds: number[]) {
  const existing = await prisma.rolePermission.findMany({
    where: { roleId },
    select: { permissionId: true },
  });
  const existingIds = new Set(existing.map((e) => e.permissionId));
  const targetIds = new Set(permissionIds);

  const missing = permissionIds.filter((id) => !existingIds.has(id));
  if (missing.length > 0) {
    await prisma.rolePermission.createMany({
      data: missing.map((permissionId) => ({ roleId, permissionId })),
      skipDuplicates: true,
    });
    console.log(`  → +${missing.length} Permissions an Rolle #${roleId}`);
  }

  const excess = existing
    .filter((e) => !targetIds.has(e.permissionId))
    .map((e) => e.permissionId);
  if (excess.length > 0) {
    await prisma.rolePermission.deleteMany({
      where: { roleId, permissionId: { in: excess } },
    });
    console.log(`  → -${excess.length} Permissions von Rolle #${roleId}`);
  }
}

async function main() {
  console.log('[sync-roles] Permissions-Katalog upserten…');
  for (const [resource, actions] of Object.entries(RESOURCE_PERMISSIONS)) {
    for (const action of actions) {
      await prisma.permission.upsert({
        where: { resource_action: { resource, action } },
        update: {},
        create: { resource, action },
      });
    }
  }
  const allPermissions = await prisma.permission.findMany();
  console.log(`[sync-roles] ${allPermissions.length} Permissions vorhanden`);

  // Admin: alles AUSSER developer:access und audit/gdpr (DSGVO + Developer
  // sind separate hidden roles, über Checkboxen zugewiesen)
  const adminPermIds = allPermissions
    .filter(
      (p) =>
        !(p.resource === 'developer' && p.action === 'access') &&
        p.resource !== 'audit' &&
        p.resource !== 'gdpr'
    )
    .map((p) => p.id);

  // Developer: alles
  const developerPermIds = allPermissions.map((p) => p.id);

  // DSGVO: Datenschutz-Verwaltung plus LESENDEN Zugriff aufs Audit-Protokoll.
  //
  // Bewusst OHNE `audit:admin`. Diese Rolle beaufsichtigt das Protokoll - sie
  // darf es nicht umschreiben koennen. Mit `audit:admin` haette ein
  // DSGVO-Beauftragter `seal-backlog`, `rehash` und `cleanup`, also die Mittel,
  // seine eigene Beweisgrundlage zu ersetzen. Wer prueft und wer eingreift,
  // sind zwei Rollen (Pentest R186, im Anschluss an R184-02: falsche Domaene,
  // zu breit gebuendelt).
  const gdprPermIds = allPermissions
    .filter(
      (p) =>
        p.resource === 'gdpr' ||
        (p.resource === 'audit' && (p.action === 'read' || p.action === 'export')),
    )
    .map((p) => p.id);

  // Audit-Betrieb: die eingreifenden Rechte am Protokoll - versiegeln,
  // neu berechnen, aufraeumen, Aufbewahrung aendern. Plus `audit:read`, denn
  // siegeln zu duerfen ohne das Ergebnis sehen zu koennen waere unbrauchbar.
  //
  // Eigene versteckte Rolle statt einem Anhaengsel an Admin: So weitet sich
  // durch die Umstellung fuer KEIN bestehendes Konto etwas aus. Wer eingreifen
  // koennen soll, bekommt es ausdruecklich - und diese Zuweisung ist selbst
  // ein sichtbarer Vorgang.
  const auditBetriebPermIds = allPermissions
    .filter(
      (p) => p.resource === 'audit' && (p.action === 'read' || p.action === 'admin'),
    )
    .map((p) => p.id);

  // Gegenbuch: NUR audit:read.
  //
  // Der externe Notar ruft genau zwei Endpunkte auf, /audit-logs/checkpoint
  // und /audit-logs/verify, und beide verlangen audit:read. Bis hierher gab es
  // dafuer keine passende Rolle: Wer dem Dienstkonto Leserechte aufs Protokoll
  // geben wollte, musste den DSGVO-Haken setzen - und der vergibt `audit:*`
  // KOMPLETT, also auch `audit:admin` mit seal-backlog, rehash und cleanup.
  //
  // Damit haette ein Einbruch auf der Gegenbuch-Maschine nicht nur den
  // Waechter gehabt, sondern gleich die Mittel, das Bewachte umzuschreiben -
  // genau die Waesche aus R185-01, und genau die Trennung, wegen der das
  // Gegenbuch ueberhaupt auf einer eigenen Maschine laeuft. Das Kennwort des
  // Dienstkontos liegt dort im Klartext in der .env; es muss deshalb so wenig
  // wert sein wie moeglich.
  const gegenbuchPermIds = allPermissions
    .filter((p) => p.resource === 'audit' && p.action === 'read')
    .map((p) => p.id);

  // Mitarbeiter: customers + contracts + read auf Stammdaten
  const employeePermIds = allPermissions
    .filter(
      (p) =>
        p.resource === 'customers' ||
        p.resource === 'contracts' ||
        (p.action === 'read' &&
          [
            'platforms',
            'providers',
            'tariffs',
            'cancellation-periods',
            'contract-durations',
            'contract-categories',
          ].includes(p.resource))
    )
    .map((p) => p.id);

  // Read-only Mitarbeiter + Kunde: nur read auf Haupt-Entities + Stammdaten
  const readOnlyResources = [
    'customers',
    'contracts',
    'platforms',
    'providers',
    'tariffs',
    'cancellation-periods',
    'contract-durations',
    'contract-categories',
  ];
  const readOnlyPermIds = allPermissions
    .filter((p) => p.action === 'read' && readOnlyResources.includes(p.resource))
    .map((p) => p.id);

  const rolesSpec: Array<{ name: string; description: string; permIds: number[] }> = [
    { name: 'Admin', description: 'Voller Zugriff auf alle Funktionen', permIds: adminPermIds },
    { name: 'Developer', description: 'Voller Zugriff inkl. Entwickler-Tools', permIds: developerPermIds },
    { name: 'DSGVO', description: 'DSGVO-Zugriff: Audit-Logs lesen und Datenschutz-Verwaltung', permIds: gdprPermIds },
    { name: 'Audit-Betrieb', description: 'Darf das Audit-Protokoll versiegeln, aufräumen und die Aufbewahrung ändern', permIds: auditBetriebPermIds },
    { name: 'Mitarbeiter', description: 'Kann Kunden und Verträge verwalten', permIds: employeePermIds },
    { name: 'Mitarbeiter (Nur-Lesen)', description: 'Kann nur lesen, keine Änderungen', permIds: readOnlyPermIds },
    { name: 'Gegenbuch', description: 'Darf das Audit-Protokoll nur lesen und prüfen – sonst nichts', permIds: gegenbuchPermIds },
    { name: 'Kunde', description: 'Kann nur eigene Daten lesen', permIds: readOnlyPermIds },
  ];

  for (const r of rolesSpec) {
    const role = await prisma.role.upsert({
      where: { name: r.name },
      update: { description: r.description },
      create: { name: r.name, description: r.description },
    });
    await syncRolePermissions(role.id, r.permIds);
  }

  console.log('[sync-roles] fertig.');
}

main()
  .catch((e) => {
    console.error('[sync-roles] Fehler:', e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });
