import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto";
import type { Encrypted, IEncryption, CreateRotator } from "./types";
import { isValidHexKey } from "./util";

const ALGORITHM = "aes-256-gcm";
const IV_LENGTH = 12;

export class Cipher implements IEncryption {
  private secretKey: Buffer;

  static createRotator: CreateRotator = (oldSecretKey, newSecretKey) => {
    const oldCipher = new Cipher(oldSecretKey);
    const newCipher = new Cipher(newSecretKey);

    const rotator = async (prevEncrypted: Encrypted): Promise<Encrypted> => {
      const deciphered = await oldCipher.decrypt(prevEncrypted);
      return await newCipher.encrypt(deciphered);
    };

    return rotator;
  };

  constructor(secretKeyHex: string) {
    if (!isValidHexKey(secretKeyHex)) {
      throw new Error("Secret key must be a valid hex string");
    }
    this.secretKey = Buffer.from(secretKeyHex, "hex");
  }

  async encrypt(text: string): Promise<Encrypted> {
    // For empty string, we still need to encrypt something
    const dataToEncrypt = text || "\0";

    const iv = randomBytes(IV_LENGTH);
    const cipher = createCipheriv(ALGORITHM, this.secretKey, iv);
    let encrypted = cipher.update(dataToEncrypt, "utf8", "hex");
    encrypted += cipher.final("hex");
    const authTag = cipher.getAuthTag().toString("hex");
    return Promise.resolve({
      iv: iv.toString("hex"),
      content: encrypted,
      authTag: authTag,
    });
  }

  async decrypt(encrypted: Encrypted): Promise<string> {
    const decipher = createDecipheriv(
      ALGORITHM,
      this.secretKey,
      Buffer.from(encrypted.iv, "hex")
    );
    decipher.setAuthTag(Buffer.from(encrypted.authTag, "hex"));
    let decrypted = decipher.update(encrypted.content, "hex", "utf8");
    decrypted += decipher.final("utf8");
    // Convert null byte back to empty string
    return Promise.resolve(decrypted === "\0" ? "" : decrypted);
  }
}
