import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'crypto';
import * as bcrypt from 'bcrypt';
import * as crypto from 'crypto';

export async function hash(data: string): Promise<string> {
  return bcrypt.hash(data, 10);
}

export async function generateApiKey(): Promise<{
  apiKey: string;
  apiSecret: string;
  apiKeyHash: string;
  apiSecretHash: string;
}> {
  const apiKey = `goa_${crypto.randomBytes(16).toString('hex')}`;
  const apiSecret = crypto.randomBytes(32).toString('hex');

  const [apiKeyHash, apiSecretHash] = await Promise.all([hash(apiKey), hash(apiSecret)]);

  return {
    apiKey,
    apiSecret,
    apiKeyHash,
    apiSecretHash,
  };
}

export class CryptoUtil {
  private static readonly ALGORITHM = 'aes-256-gcm';
  private static readonly SALT_LENGTH = 16;
  private static readonly TAG_LENGTH = 16;
  private static readonly IV_LENGTH = 16;

  static encrypt(data: string, password: string): string {
    const salt = randomBytes(CryptoUtil.SALT_LENGTH);
    const key = scryptSync(password, salt, 32);
    const iv = randomBytes(CryptoUtil.IV_LENGTH);

    const cipher = createCipheriv(CryptoUtil.ALGORITHM, key, iv);
    const encrypted = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]);

    const authTag = cipher.getAuthTag();

    return Buffer.concat([salt, iv, authTag, encrypted]).toString('hex');
  }

  static decrypt(encryptedData: string, password: string): string {
    const buffer = Buffer.from(encryptedData, 'hex');

    const salt = buffer.subarray(0, CryptoUtil.SALT_LENGTH);
    const iv = buffer.subarray(
      CryptoUtil.SALT_LENGTH,
      CryptoUtil.SALT_LENGTH + CryptoUtil.IV_LENGTH,
    );
    const authTag = buffer.subarray(
      CryptoUtil.SALT_LENGTH + CryptoUtil.IV_LENGTH,
      CryptoUtil.SALT_LENGTH + CryptoUtil.IV_LENGTH + CryptoUtil.TAG_LENGTH,
    );
    const encrypted = buffer.subarray(
      CryptoUtil.SALT_LENGTH + CryptoUtil.IV_LENGTH + CryptoUtil.TAG_LENGTH,
    );

    const key = scryptSync(password, salt, 32);
    const decipher = createDecipheriv(CryptoUtil.ALGORITHM, key, iv);
    decipher.setAuthTag(authTag);

    return decipher.update(encrypted) + decipher.final('utf8');
  }

  static generateKey(length: number = 32): string {
    return randomBytes(length).toString('hex');
  }

  static generateIV(length: number = 16): string {
    return randomBytes(length).toString('hex');
  }
}
