import base62 from '@fry/base62';
import cbor from 'cbor';
import * as jose from 'jose';

import { cbor2jose, jose2cbor } from '../../cbor-jose.js';
import * as lzma from '../../lzma.js';
import { crypto, hex, pbkdf2, sha256, sha256hmac } from '../../utils.js';

export const PROTOCOL_ID = 'mB:pigi2';

interface PigiV2 {
  _secret1: Uint8Array;
  _secret1Hex: string;
  _secret1Base62: string;
  _secret2: Uint8Array;
  _secret2Hex: string;
  _secret2Base62: string;
  encryptedData: Uint8Array;
}

export async function create(
  data: Uint8Array,
  signingKeys: Array<CryptoKey>,
  options: {
    filename: string;
    mimetype: string;
  },
): Promise<PigiV2> {
  const hash = await sha256(data);
  const iterations = 200000;
  const cryptoKey = await pbkdf2(data, hash, 512, iterations);

  const secret1 = cryptoKey.slice(0, 32);
  const secret2 = cryptoKey.slice(32);

  const rawCek = await sha256hmac(secret1, secret2);
  const cek = await crypto.subtle.importKey('raw', rawCek, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt']);

  const payload = await cbor.encodeAsync([options.filename, options.mimetype, data]);

  const compressed = await lzma.compress(payload, 9);

  const l0 = await new jose.GeneralEncrypt(compressed)
    .setProtectedHeader({ alg: 'dir', enc: 'A256GCM', iter: iterations, salt: hash })
    .addRecipient(cek)
    .setUnprotectedHeader({ fid: 'bla' }) // add key id
    .encrypt();

  const cl0 = await jose2cbor(l0);

  const _l1 = await new jose.GeneralSign(cl0);

  for (const signingKey of signingKeys) {
    const jwk = await jose.exportJWK(signingKey);
    const kid = await jose.calculateJwkThumbprint(jwk);
    _l1.addSignature(signingKey).setProtectedHeader({ alg: 'ES256', kid: kid });
  }

  const l1 = await _l1.sign();

  const l2 = await new jose.GeneralEncrypt(await jose2cbor(l1))
    .setProtectedHeader({ alg: 'dir', enc: 'A128CBC-HS256' })
    .addRecipient(new Uint8Array(secret1))
    .setUnprotectedHeader({ fid: 'bla2' })
    .encrypt();

  const final = await jose2cbor(l2);

  return {
    _secret1: secret1,
    _secret1Hex: hex.bufToHex(secret1),
    _secret1Base62: base62.encode(secret1),
    _secret2: secret2,
    _secret2Hex: hex.bufToHex(secret2),
    _secret2Base62: base62.encode(secret2),
    encryptedData: final,
  };
}

export async function parse(
  data: Uint8Array,
  secret1: Uint8Array,
  secret2: Uint8Array,
  verifyKey: jose.KeyLike | jose.GeneralVerifyGetKey,
) {
  const rawCek = await sha256hmac(secret1, secret2);
  const cek = await crypto.subtle.importKey('raw', rawCek, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt']);

  const l2 = await cbor2jose(data);

  const l2decrypted = await jose.generalDecrypt(l2, secret1);

  const l1 = await cbor2jose(l2decrypted.plaintext);

  const l1verify = await jose.generalVerify(l1, verifyKey as any);

  const l1verified = l1verify.payload;

  const l0 = await cbor2jose(l1verified);

  const l0decrypted = await jose.generalDecrypt(l0, cek);

  const l0decrypteddata = l0decrypted.plaintext;

  const decompressed = await lzma.decompress(l0decrypteddata);

  const [filename, mimetype, originaldata] = cbor.decode(decompressed);

  return {
    _filename: filename,
    _mimetype: mimetype,
    _plainData: originaldata,
  };
}
