import fs from "node:fs";
import path from "node:path";
import {Keystore} from "@chainsafe/bls-keystore";
import {SecretKey} from "@chainsafe/blst";
import {SignerLocal, SignerType} from "@lodestar/validator";
import {fromHex, toHex, toPubkeyHex} from "@lodestar/utils";
import {writeFile600Perm} from "../../../util/file.js";
import {lockFilepath, unlockFilepath} from "../../../util/lockfile.js";
import {LocalKeystoreDefinition} from "./interface.js";

export async function loadKeystoreCache(
  cacheFilepath: string,
  keystoreDefinitions: LocalKeystoreDefinition[]
): Promise<SignerLocal[]> {
  const keystores: Keystore[] = [];
  const passwords: string[] = [];
  for (const {keystorePath, password} of keystoreDefinitions) {
    keystores.push(Keystore.parse(fs.readFileSync(keystorePath, "utf8")));
    passwords.push(password);
  }

  if (keystores.length !== passwords.length) {
    throw new Error(
      `Number of keystores and passwords must be equal. keystores=${keystores.length}, passwords=${passwords.length}`
    );
  }

  if (!fs.existsSync(cacheFilepath)) {
    throw new Error(`Cache file ${cacheFilepath} does not exists.`);
  }

  lockFilepath(cacheFilepath);

  const password = passwords.join("");
  // We can't use Keystore.parse as it validates the `encrypted message` to be only 32 bytes.
  const keystore = new Keystore(JSON.parse(fs.readFileSync(cacheFilepath, "utf8")));
  const secretKeyConcatenatedBytes = await keystore.decrypt(password);

  const result: SignerLocal[] = [];
  for (const [index, k] of keystores.entries()) {
    const secretKeyBytes = Uint8Array.prototype.slice.call(secretKeyConcatenatedBytes, index * 32, (index + 1) * 32);
    const secretKey = SecretKey.fromBytes(secretKeyBytes);
    const publicKey = secretKey.toPublicKey().toBytes();

    if (toPubkeyHex(publicKey) !== toPubkeyHex(fromHex(k.pubkey))) {
      throw new Error(
        `Keystore ${k.uuid} does not match the expected pubkey. expected=${toPubkeyHex(fromHex(k.pubkey))}, found=${toHex(
          publicKey
        )}`
      );
    }

    result.push({
      type: SignerType.Local,
      secretKey,
    });
  }

  unlockFilepath(cacheFilepath);

  return result;
}

export async function writeKeystoreCache(
  cacheFilepath: string,
  signers: SignerLocal[],
  passwords: string[]
): Promise<void> {
  if (signers.length !== passwords.length) {
    throw new Error(
      `Number of signers and passwords must be equal. signers=${signers.length}, passwords=${passwords.length}`
    );
  }
  const secretKeys = signers.map((s) => s.secretKey.toBytes());
  const publicKeys = signers.map((s) => s.secretKey.toPublicKey().toBytes());
  const password = passwords.join("");
  const secretKeyConcatenatedBytes = Buffer.concat(secretKeys);
  const publicConcatenatedBytes = Buffer.concat(publicKeys);
  const keystore = await Keystore.create(password, secretKeyConcatenatedBytes, publicConcatenatedBytes, cacheFilepath);
  if (!fs.existsSync(path.dirname(cacheFilepath))) fs.mkdirSync(path.dirname(cacheFilepath), {recursive: true});
  lockFilepath(cacheFilepath);
  writeFile600Perm(cacheFilepath, keystore.stringify());
  unlockFilepath(cacheFilepath);
}

export async function clearKeystoreCache(cacheFilepath: string): Promise<void> {
  if (fs.existsSync(cacheFilepath)) {
    unlockFilepath(cacheFilepath);
    fs.unlinkSync(cacheFilepath);
  }
}
