import * as crypto from 'crypto';

const EXCLUDE_FIELDS = new Set([
  'sign',
  'sign_type',
  'header',
  'refund_info',
  'openType',
  'raw_request',
  'biz_content',
]);

const NONCE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

export function buildCanonicalString(requestObject: Record<string, unknown>): string {
  const fieldMap: Record<string, unknown> = {};

  for (const key of Object.keys(requestObject)) {
    if (EXCLUDE_FIELDS.has(key)) continue;
    if (requestObject[key] === undefined) continue;
    fieldMap[key] = requestObject[key];
  }

  const biz = requestObject['biz_content'];
  if (biz && typeof biz === 'object') {
    for (const key of Object.keys(biz as Record<string, unknown>)) {
      if (EXCLUDE_FIELDS.has(key)) continue;
      const value = (biz as Record<string, unknown>)[key];
      if (value === undefined) continue;
      fieldMap[key] = value;
    }
  }

  return Object.keys(fieldMap)
    .sort()
    .map((k) => `${k}=${fieldMap[k]}`)
    .join('&');
}

export function signRequestObject(
  requestObject: Record<string, unknown>,
  privateKey: string,
): string {
  return signString(buildCanonicalString(requestObject), privateKey);
}

export function verifyRequestObject(
  requestObject: Record<string, unknown>,
  publicKey: string,
): boolean {
  const signature = requestObject['sign'];
  if (typeof signature !== 'string' || signature.length === 0) return false;
  return verifySignature(buildCanonicalString(requestObject), signature, publicKey);
}

export function signString(text: string, privateKey: string): string {
  const signature = crypto.sign('sha256', Buffer.from(text), {
    key: privateKey,
    padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
    saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST,
  });
  return signature.toString('base64');
}

export function verifySignature(
  text: string,
  signatureBase64: string,
  publicKey: string,
): boolean {
  try {
    return crypto.verify(
      'sha256',
      Buffer.from(text),
      {
        key: publicKey,
        padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
        saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST,
      },
      Buffer.from(signatureBase64, 'base64'),
    );
  } catch {
    return false;
  }
}

export function createTimestamp(): string {
  return Math.round(Date.now() / 1000).toString();
}

export function createNonceStr(length = 32): string {
  const bytes = crypto.randomBytes(length);
  let out = '';
  for (let i = 0; i < length; i++) {
    out += NONCE_CHARS[bytes[i] % NONCE_CHARS.length];
  }
  return out;
}

export function createMerchantOrderId(): string {
  return `${Date.now()}${crypto.randomBytes(4).toString('hex')}`;
}
