/**
 * Validates that critical environment variables are set at application startup.
 *
 * Security-sensitive vars (JWT secrets) are required in ALL environments and must
 * not be placeholder/weak values. Infrastructure vars (DATABASE_URL, CORS, Redis)
 * are required only in production.
 */

const ALWAYS_REQUIRED: readonly string[] = [
  'JWT_SECRET',
  'JWT_REFRESH_SECRET',
];

const REQUIRED_IN_PRODUCTION: readonly string[] = [
  'DATABASE_URL',
  'CORS_ORIGINS',
  'REDIS_HOST',
  'FIELD_ENCRYPTION_KEY',
];

const REQUIRED_WHEN_USED: ReadonlyMap<string, string> = new Map([
  ['VNPAY_TMN_CODE', 'VNPay payments'],
  ['VNPAY_HASH_SECRET', 'VNPay payments'],
  ['MOMO_PARTNER_CODE', 'MoMo payments'],
  ['MOMO_ACCESS_KEY', 'MoMo payments'],
  ['MOMO_SECRET_KEY', 'MoMo payments'],
  ['ZALOPAY_APP_ID', 'ZaloPay payments'],
  ['ZALOPAY_KEY1', 'ZaloPay payments'],
  ['ZALOPAY_KEY2', 'ZaloPay payments'],
  ['BANK_TRANSFER_ACCOUNT_NUMBER', 'Bank transfer payments'],
  ['BANK_TRANSFER_BANK_NAME', 'Bank transfer payments'],
  ['BANK_TRANSFER_ACCOUNT_HOLDER', 'Bank transfer payments'],
  ['BANK_TRANSFER_WEBHOOK_SECRET', 'Bank transfer payments'],
  ['MINIO_ACCESS_KEY', 'Media storage'],
  ['MINIO_SECRET_KEY', 'Media storage'],
  ['GOOGLE_CLIENT_ID', 'Google OAuth'],
  ['GOOGLE_CLIENT_SECRET', 'Google OAuth'],
  ['ZALO_APP_ID', 'Zalo OAuth'],
  ['ZALO_APP_SECRET', 'Zalo OAuth'],
  ['ZALO_OA_ID', 'Zalo OA notifications'],
  ['ZALO_OA_ACCESS_TOKEN', 'Zalo OA notifications'],
]);

/**
 * Known placeholder values that must never be used as real secrets.
 * Comparison is case-insensitive to catch common variants.
 */
const FORBIDDEN_SECRET_VALUES: readonly string[] = [
  'change_me',
  'changeme',
  'your_jwt_secret',
  'your_jwt_secret_change_me',
  'your_jwt_refresh_secret',
  'your_jwt_refresh_secret_change_me',
  'secret',
  'jwt_secret',
  'jwt_refresh_secret',
  'supersecret',
  'super_secret',
  'mysecret',
  'my_secret',
  'password',
  'test',
  'default',
  'placeholder',
  'replace_me',
  'replaceme',
  'fixme',
  'todo',
  'xxx',
];

/** Minimum length for JWT secrets — NIST SP 800-117 recommends ≥256 bits for HMAC keys. */
const MIN_SECRET_LENGTH = 32;

/**
 * Validates that a JWT secret is not a placeholder value, is long enough, and
 * does not appear to be a trivially weak key.
 *
 * @returns An error message string if the value is invalid, or `null` if OK.
 */
export function validateJwtSecret(key: string, value: string): string | null {
  if (FORBIDDEN_SECRET_VALUES.includes(value.toLowerCase().trim())) {
    return `${key} is set to a known placeholder value ("${value}"). Generate a real secret: \`openssl rand -base64 48\``;
  }

  if (value.length < MIN_SECRET_LENGTH) {
    return `${key} is too short (${value.length} chars, minimum ${MIN_SECRET_LENGTH}). Generate a strong secret: \`openssl rand -base64 48\``;
  }

  return null;
}

export function validateEnv(): void {
  const isProduction = process.env['NODE_ENV'] === 'production';
  const missing: string[] = [];

  // JWT secrets are required in every environment — a missing secret is a
  // security risk regardless of NODE_ENV.
  for (const key of ALWAYS_REQUIRED) {
    if (!process.env[key]) {
      missing.push(key);
    }
  }

  if (missing.length > 0) {
    throw new Error(
      `Missing required environment variables:\n  ${missing.join('\n  ')}\n` +
        'JWT_SECRET and JWT_REFRESH_SECRET must always be set. See .env.example.',
    );
  }

  // Reject placeholder / weak JWT secret values — these are dangerous even in
  // development because tokens signed with predictable keys are trivially forgeable.
  const secretErrors: string[] = [];
  for (const key of ALWAYS_REQUIRED) {
    const error = validateJwtSecret(key, process.env[key]!);
    if (error) {
      secretErrors.push(error);
    }
  }

  if (secretErrors.length > 0) {
    throw new Error(
      `Insecure JWT secret configuration:\n  ${secretErrors.join('\n  ')}\n` +
        'Generate secure secrets with: openssl rand -base64 48',
    );
  }

  if (!isProduction) {
    return;
  }

  // Infrastructure vars — required in production only.
  const missingProd: string[] = [];
  for (const key of REQUIRED_IN_PRODUCTION) {
    if (!process.env[key]) {
      missingProd.push(key);
    }
  }

  if (missingProd.length > 0) {
    throw new Error(
      `Missing required environment variables in production:\n  ${missingProd.join('\n  ')}\n` +
        'See .env.example for the full list of variables.',
    );
  }

  // Warn about payment/storage vars that are empty — services will fail at
  // runtime when invoked, but we surface the warning early.
  const warnings: string[] = [];
  for (const [key, feature] of REQUIRED_WHEN_USED) {
    if (!process.env[key]) {
      warnings.push(`  ${key} (${feature})`);
    }
  }
  if (warnings.length > 0) {
    console.warn(
      `[env-validation] The following optional vars are unset — related features will fail at runtime:\n${warnings.join('\n')}`,
    );
  }
}
