import { PrismaClient, Prisma } from '@prisma/client';
import { setBeforeValues, setAfterValues } from '../middleware/auditContext.js';

// DATABASE_URL aus DB_*-Komponenten bauen, falls nicht explizit gesetzt.
// Der entrypoint.sh macht das ebenfalls (für den Server-Start). Aber bei
// `docker exec opencrm-app npx tsx prisma/<script>.ts` läuft eine neue
// Shell ohne diese exportierte Variable – die DB_*-Vars sind aus dem
// docker-compose.yml vererbt, DATABASE_URL aber nicht. Damit alle
// Wartungsskripte (reset-admin-password, cleanup-xss-...) und Server
// dieselbe Logik nutzen, machen wir es einmal zentral hier.
if (!process.env.DATABASE_URL && process.env.DB_USER && process.env.DB_PASSWORD && process.env.DB_NAME) {
  const u = encodeURIComponent(process.env.DB_USER);
  const p = encodeURIComponent(process.env.DB_PASSWORD);
  const h = process.env.DB_HOST || 'db';
  const port = process.env.DB_PORT || '3306';
  const n = process.env.DB_NAME;
  process.env.DATABASE_URL = `mysql://${u}:${p}@${h}:${port}/${n}`;
}

// Modelle die für Before/After-Tracking relevant sind
const AUDITED_MODELS = [
  'Customer',
  'Contract',
  'Address',
  'BankCard',
  'IdentityDocument',
  'User',
  'Meter',
  'MeterReading',
  'StressfreiEmail',
  'Provider',
  'Tariff',
  'ContractCategory',
  'AppSetting',
  'CustomerConsent',
  'EnergyContractDetails',
  'RepresentativeAuthorization',
  'ContractMeter',
  'EmailProviderConfig',
  'ContractTask',
];

// Sensible Felder die aus dem Audit-Log gefiltert werden
const SENSITIVE_FIELDS = [
  'password',
  'passwordHash',
  'portalPasswordHash',
  'portalPasswordEncrypted',
  'emailPasswordEncrypted',
  'internetPasswordEncrypted',
  'sipPasswordEncrypted',
  'pin',
  'puk',
  'apiKey',
];

/**
 * Filtert sensible Felder aus einem Objekt
 */
function filterSensitiveFields(obj: Record<string, unknown>): Record<string, unknown> {
  const filtered: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(obj)) {
    if (SENSITIVE_FIELDS.includes(key)) {
      filtered[key] = '[REDACTED]';
    } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
      filtered[key] = filterSensitiveFields(value as Record<string, unknown>);
    } else {
      filtered[key] = value;
    }
  }
  return filtered;
}

/**
 * Prüft ob ein Model für Audit-Tracking relevant ist
 */
function isAuditedModel(model: string | undefined): boolean {
  return model !== undefined && AUDITED_MODELS.includes(model);
}

/**
 * Erstellt einen Prisma Client mit Audit-Middleware
 */
function createPrismaClient(): PrismaClient {
  const prisma = new PrismaClient();

  // Middleware für Before/After-Tracking
  prisma.$use(async (params: Prisma.MiddlewareParams, next: (params: Prisma.MiddlewareParams) => Promise<unknown>) => {
    const { model, action, args } = params;

    // Nur relevante Modelle und Aktionen tracken
    if (!isAuditedModel(model)) {
      return next(params);
    }

    // Update-Operationen: Vorherigen Stand abrufen
    if (action === 'update' || action === 'updateMany') {
      try {
        const modelDelegate = (prisma as unknown as Record<string, { findUnique: (args: unknown) => Promise<unknown> }>)[
          model!.charAt(0).toLowerCase() + model!.slice(1)
        ];

        if (modelDelegate && args?.where) {
          const before = await modelDelegate.findUnique({ where: args.where });
          if (before) {
            setBeforeValues(filterSensitiveFields(before as Record<string, unknown>));
          }
        }
      } catch {
        // Fehler beim Abrufen des vorherigen Stands ignorieren
      }
    }

    // Delete-Operationen: Datensatz vor dem Löschen abrufen
    if (action === 'delete' || action === 'deleteMany') {
      try {
        const modelDelegate = (prisma as unknown as Record<string, { findUnique: (args: unknown) => Promise<unknown> }>)[
          model!.charAt(0).toLowerCase() + model!.slice(1)
        ];

        if (modelDelegate && args?.where) {
          const before = await modelDelegate.findUnique({ where: args.where });
          if (before) {
            setBeforeValues(filterSensitiveFields(before as Record<string, unknown>));
          }
        }
      } catch {
        // Fehler beim Abrufen ignorieren
      }
    }

    // Operation ausführen
    const result = await next(params);

    // Nach Update/Create: Neuen Stand speichern
    if ((action === 'update' || action === 'create') && result) {
      setAfterValues(filterSensitiveFields(result as Record<string, unknown>));
    }

    return result;
  });

  return prisma;
}

// Singleton-Instanz
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined };

export const prisma = globalForPrisma.prisma ?? createPrismaClient();

if (process.env.NODE_ENV !== 'production') {
  globalForPrisma.prisma = prisma;
}

export default prisma;
