// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import {
  AuthCredential,
  ClientZkAuthOperations,
  ClientZkGroupCipher,
  ClientZkProfileOperations,
  FFICompatArray,
  FFICompatArrayType,
  GroupMasterKey,
  GroupSecretParams,
  ProfileKey,
  ProfileKeyCiphertext,
  ProfileKeyCredential,
  ProfileKeyCredentialPresentation,
  ProfileKeyCredentialRequestContext,
  ProfileKeyCredentialResponse,
  ServerPublicParams,
  UuidCiphertext,
} from 'zkgroup';
import {
  arrayBufferToBase64,
  arrayBufferToHex,
  base64ToArrayBuffer,
  typedArrayToArrayBuffer,
} from '../Crypto';

export * from 'zkgroup';

export function arrayBufferToCompatArray(
  arrayBuffer: ArrayBuffer
): FFICompatArrayType {
  const buffer = Buffer.from(arrayBuffer);

  return new FFICompatArray(buffer);
}

export function compatArrayToArrayBuffer(
  compatArray: FFICompatArrayType
): ArrayBuffer {
  return typedArrayToArrayBuffer(compatArray.buffer);
}

export function base64ToCompatArray(base64: string): FFICompatArrayType {
  return arrayBufferToCompatArray(base64ToArrayBuffer(base64));
}

export function compatArrayToBase64(compatArray: FFICompatArrayType): string {
  return arrayBufferToBase64(compatArrayToArrayBuffer(compatArray));
}

export function compatArrayToHex(compatArray: FFICompatArrayType): string {
  return arrayBufferToHex(compatArrayToArrayBuffer(compatArray));
}

// Scenarios

export function decryptGroupBlob(
  clientZkGroupCipher: ClientZkGroupCipher,
  ciphertext: ArrayBuffer
): ArrayBuffer {
  return compatArrayToArrayBuffer(
    clientZkGroupCipher.decryptBlob(arrayBufferToCompatArray(ciphertext))
  );
}

export function decryptProfileKeyCredentialPresentation(
  clientZkGroupCipher: ClientZkGroupCipher,
  presentationBuffer: ArrayBuffer
): { profileKey: ArrayBuffer; uuid: string } {
  const presentation = new ProfileKeyCredentialPresentation(
    arrayBufferToCompatArray(presentationBuffer)
  );

  const uuidCiphertext = presentation.getUuidCiphertext();
  const uuid = clientZkGroupCipher.decryptUuid(uuidCiphertext);

  const profileKeyCiphertext = presentation.getProfileKeyCiphertext();
  const profileKey = clientZkGroupCipher.decryptProfileKey(
    profileKeyCiphertext,
    uuid
  );

  return {
    profileKey: compatArrayToArrayBuffer(profileKey.serialize()),
    uuid,
  };
}

export function decryptProfileKey(
  clientZkGroupCipher: ClientZkGroupCipher,
  profileKeyCiphertextBuffer: ArrayBuffer,
  uuid: string
): ArrayBuffer {
  const profileKeyCiphertext = new ProfileKeyCiphertext(
    arrayBufferToCompatArray(profileKeyCiphertextBuffer)
  );

  const profileKey = clientZkGroupCipher.decryptProfileKey(
    profileKeyCiphertext,
    uuid
  );

  return compatArrayToArrayBuffer(profileKey.serialize());
}

export function decryptUuid(
  clientZkGroupCipher: ClientZkGroupCipher,
  uuidCiphertextBuffer: ArrayBuffer
): string {
  const uuidCiphertext = new UuidCiphertext(
    arrayBufferToCompatArray(uuidCiphertextBuffer)
  );

  return clientZkGroupCipher.decryptUuid(uuidCiphertext);
}

export function deriveProfileKeyVersion(
  profileKeyBase64: string,
  uuid: string
): string {
  const profileKeyArray = base64ToCompatArray(profileKeyBase64);
  const profileKey = new ProfileKey(profileKeyArray);

  const profileKeyVersion = profileKey.getProfileKeyVersion(uuid);

  return profileKeyVersion.toString();
}

export function deriveGroupPublicParams(
  groupSecretParamsBuffer: ArrayBuffer
): ArrayBuffer {
  const groupSecretParams = new GroupSecretParams(
    arrayBufferToCompatArray(groupSecretParamsBuffer)
  );

  return compatArrayToArrayBuffer(
    groupSecretParams.getPublicParams().serialize()
  );
}

export function deriveGroupID(
  groupSecretParamsBuffer: ArrayBuffer
): ArrayBuffer {
  const groupSecretParams = new GroupSecretParams(
    arrayBufferToCompatArray(groupSecretParamsBuffer)
  );

  return compatArrayToArrayBuffer(
    groupSecretParams.getPublicParams().getGroupIdentifier().serialize()
  );
}

export function deriveGroupSecretParams(
  masterKeyBuffer: ArrayBuffer
): ArrayBuffer {
  const masterKey = new GroupMasterKey(
    arrayBufferToCompatArray(masterKeyBuffer)
  );
  const groupSecretParams = GroupSecretParams.deriveFromMasterKey(masterKey);

  return compatArrayToArrayBuffer(groupSecretParams.serialize());
}

export function encryptGroupBlob(
  clientZkGroupCipher: ClientZkGroupCipher,
  plaintext: ArrayBuffer
): ArrayBuffer {
  return compatArrayToArrayBuffer(
    clientZkGroupCipher.encryptBlob(arrayBufferToCompatArray(plaintext))
  );
}

export function encryptUuid(
  clientZkGroupCipher: ClientZkGroupCipher,
  uuidPlaintext: string
): ArrayBuffer {
  const uuidCiphertext = clientZkGroupCipher.encryptUuid(uuidPlaintext);

  return compatArrayToArrayBuffer(uuidCiphertext.serialize());
}

export function generateProfileKeyCredentialRequest(
  clientZkProfileCipher: ClientZkProfileOperations,
  uuid: string,
  profileKeyBase64: string
): { context: ProfileKeyCredentialRequestContext; requestHex: string } {
  const profileKeyArray = base64ToCompatArray(profileKeyBase64);
  const profileKey = new ProfileKey(profileKeyArray);

  const context = clientZkProfileCipher.createProfileKeyCredentialRequestContext(
    uuid,
    profileKey
  );
  const request = context.getRequest();
  const requestArray = request.serialize();

  return {
    context,
    requestHex: compatArrayToHex(requestArray),
  };
}

export function getAuthCredentialPresentation(
  clientZkAuthOperations: ClientZkAuthOperations,
  authCredentialBase64: string,
  groupSecretParamsBase64: string
): ArrayBuffer {
  const authCredential = new AuthCredential(
    base64ToCompatArray(authCredentialBase64)
  );
  const secretParams = new GroupSecretParams(
    base64ToCompatArray(groupSecretParamsBase64)
  );

  const presentation = clientZkAuthOperations.createAuthCredentialPresentation(
    secretParams,
    authCredential
  );
  return compatArrayToArrayBuffer(presentation.serialize());
}

export function createProfileKeyCredentialPresentation(
  clientZkProfileCipher: ClientZkProfileOperations,
  profileKeyCredentialBase64: string,
  groupSecretParamsBase64: string
): ArrayBuffer {
  const profileKeyCredentialArray = base64ToCompatArray(
    profileKeyCredentialBase64
  );
  const profileKeyCredential = new ProfileKeyCredential(
    profileKeyCredentialArray
  );
  const secretParams = new GroupSecretParams(
    base64ToCompatArray(groupSecretParamsBase64)
  );

  const presentation = clientZkProfileCipher.createProfileKeyCredentialPresentation(
    secretParams,
    profileKeyCredential
  );

  return compatArrayToArrayBuffer(presentation.serialize());
}

export function getClientZkAuthOperations(
  serverPublicParamsBase64: string
): ClientZkAuthOperations {
  const serverPublicParams = new ServerPublicParams(
    base64ToCompatArray(serverPublicParamsBase64)
  );

  return new ClientZkAuthOperations(serverPublicParams);
}

export function getClientZkGroupCipher(
  groupSecretParamsBase64: string
): ClientZkGroupCipher {
  const serverPublicParams = new GroupSecretParams(
    base64ToCompatArray(groupSecretParamsBase64)
  );

  return new ClientZkGroupCipher(serverPublicParams);
}

export function getClientZkProfileOperations(
  serverPublicParamsBase64: string
): ClientZkProfileOperations {
  const serverPublicParams = new ServerPublicParams(
    base64ToCompatArray(serverPublicParamsBase64)
  );

  return new ClientZkProfileOperations(serverPublicParams);
}

export function handleProfileKeyCredential(
  clientZkProfileCipher: ClientZkProfileOperations,
  context: ProfileKeyCredentialRequestContext,
  responseBase64: string
): string {
  const response = new ProfileKeyCredentialResponse(
    base64ToCompatArray(responseBase64)
  );
  const profileKeyCredential = clientZkProfileCipher.receiveProfileKeyCredential(
    context,
    response
  );

  const credentialArray = profileKeyCredential.serialize();

  return compatArrayToBase64(credentialArray);
}
