// XFTP client protocol operations -- Simplex.FileTransfer.Client + Crypto
//
// CbAuthenticator-based command authentication and transport-level
// chunk encryption/decryption for XFTP downloads.

import {concatBytes} from "./encoding.js"
import {dh} from "../crypto/keys.js"
import {sha512} from "../crypto/digest.js"
import {
  cbInit, sbEncryptChunk, sbDecryptChunk, sbAuth, cryptoBox
} from "../crypto/secretbox.js"

// -- Constants

export const cbAuthenticatorSize = 80 // SHA512 (64) + authTag (16)

// -- CbAuthenticator (Crypto.hs:cbAuthenticate)

// Create crypto_box authenticator for a message.
// Encrypts sha512(msg) with NaCl crypto_box using DH(peerPubKey, ownPrivKey).
// Returns 80 bytes (16-byte tag prepended + 64-byte encrypted hash).
export function cbAuthenticate(
  peerPubKey: Uint8Array,
  ownPrivKey: Uint8Array,
  nonce: Uint8Array,
  msg: Uint8Array
): Uint8Array {
  const dhSecret = dh(peerPubKey, ownPrivKey)
  const hash = sha512(msg)
  return cryptoBox(dhSecret, nonce, hash)
}

// Verify crypto_box authenticator for a message.
// Decrypts authenticator with DH(peerPubKey, ownPrivKey), checks against sha512(msg).
export function cbVerify(
  peerPubKey: Uint8Array,
  ownPrivKey: Uint8Array,
  nonce: Uint8Array,
  authenticator: Uint8Array,
  msg: Uint8Array
): boolean {
  if (authenticator.length !== cbAuthenticatorSize) return false
  const dhSecret = dh(peerPubKey, ownPrivKey)
  const tag = authenticator.subarray(0, 16)
  const cipher = authenticator.subarray(16)
  const state = cbInit(dhSecret, nonce)
  const plaintext = sbDecryptChunk(state, cipher)
  const computedTag = sbAuth(state)
  if (!constantTimeEqual(tag, computedTag)) return false
  const expectedHash = sha512(msg)
  return constantTimeEqual(plaintext, expectedHash)
}

// -- Transport-level chunk encryption/decryption

// Encrypt a chunk for transport (tag-appended format).
// Matches sendEncFile in FileTransfer.Transport:
//   ciphertext streamed via sbEncryptChunk, then 16-byte auth tag appended.
export function encryptTransportChunk(
  dhSecret: Uint8Array,
  cbNonce: Uint8Array,
  plainData: Uint8Array
): Uint8Array {
  const state = cbInit(dhSecret, cbNonce)
  const cipher = sbEncryptChunk(state, plainData)
  const tag = sbAuth(state)
  return concatBytes(cipher, tag)
}

// Decrypt a transport-encrypted chunk (tag-appended format).
// Matches receiveEncFile / receiveSbFile in FileTransfer.Transport:
//   ciphertext decrypted via sbDecryptChunk, then 16-byte auth tag verified.
export function decryptTransportChunk(
  dhSecret: Uint8Array,
  cbNonce: Uint8Array,
  encData: Uint8Array
): {valid: boolean, content: Uint8Array, computedTag: Uint8Array} {
  if (encData.length < 16) return {valid: false, content: new Uint8Array(0), computedTag: new Uint8Array(0)}
  const cipher = encData.subarray(0, encData.length - 16)
  const providedTag = encData.subarray(encData.length - 16)
  const state = cbInit(dhSecret, cbNonce)
  const plaintext = sbDecryptChunk(state, cipher)
  const computedTag = sbAuth(state)
  const valid = constantTimeEqual(providedTag, computedTag)
  return {valid, content: plaintext, computedTag}
}

// -- Internal

function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {
  if (a.length !== b.length) return false
  let diff = 0
  for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i]
  return diff === 0
}
