import { Request, Response, NextFunction } from 'express';
import { prisma } from '../../config/database';
import type { PaymentSettings } from '@prisma/client';
import type { UpdatePaymentSettingsInput } from './payments.schemas';
import { encrypt, decrypt } from '../../utils/crypto';
import { resetStripeClient } from '../../services/stripe.client';

const ENCRYPTED_FIELDS = ['stripeSecretKey', 'stripeWebhookSecret'] as const;
const SENSITIVE_FIELDS = ['stripeSecretKey', 'stripeWebhookSecret'] as const;

function decryptSettings(settings: PaymentSettings): PaymentSettings {
  for (const field of ENCRYPTED_FIELDS) {
    const value = settings[field];
    if (typeof value === 'string' && value) {
      (settings as Record<string, unknown>)[field] = decrypt(value);
    }
  }
  return settings;
}

export const paymentSettingsService = {
  /** Full settings with decrypted secrets (admin use) */
  async get(): Promise<PaymentSettings> {
    let settings = await prisma.paymentSettings.findFirst();
    if (!settings) {
      settings = await prisma.paymentSettings.create({ data: {} });
    }
    return decryptSettings(settings);
  },

  /** Public-safe settings (strips secret keys) */
  async getPublic() {
    const settings = await this.get();
    const result = { ...settings } as Record<string, unknown>;
    for (const field of SENSITIVE_FIELDS) {
      delete result[field];
    }
    return result;
  },

  async update(data: UpdatePaymentSettingsInput): Promise<PaymentSettings> {
    const toWrite = { ...data } as Record<string, unknown>;

    // Encrypt sensitive fields, skipping masked sentinel values from the admin UI
    for (const field of ENCRYPTED_FIELDS) {
      if (field in toWrite && typeof toWrite[field] === 'string') {
        const val = toWrite[field] as string;
        if (!val || val.startsWith('••••')) {
          // Empty or mask string submitted — preserve existing encrypted value
          delete toWrite[field];
          continue;
        }
        toWrite[field] = encrypt(val);
      }
    }

    // Handle donationSuggestedAmounts as JSON
    if (data.donationSuggestedAmounts) {
      toWrite.donationSuggestedAmounts = JSON.stringify(data.donationSuggestedAmounts);
    }

    const existing = await prisma.paymentSettings.findFirst();
    let settings: PaymentSettings;
    if (existing) {
      settings = await prisma.paymentSettings.update({
        where: { id: existing.id },
        data: toWrite,
      });
    } else {
      settings = await prisma.paymentSettings.create({ data: toWrite });
    }

    // Reset Stripe client so it picks up new keys
    resetStripeClient();

    return decryptSettings(settings);
  },
};

/** Middleware: reject requests when payments are disabled in site settings */
export async function requirePaymentsEnabled(_req: Request, res: Response, next: NextFunction) {
  try {
    const settings = await prisma.siteSettings.findFirst({ select: { enablePayments: true } });
    if (!settings?.enablePayments) {
      res.status(403).json({ error: { message: 'Payments are not enabled', code: 'PAYMENTS_DISABLED' } });
      return;
    }
    next();
  } catch (err) {
    next(err);
  }
}
