import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { BufferJSON, initAuthCreds, proto, } from "@whiskeysockets/baileys";
function keyFrom(secret) {
    return createHash("sha256").update(secret, "utf8").digest();
}
export class EncryptedFileStore {
    directory;
    key;
    constructor(directory, secret) {
        this.directory = directory;
        this.key = keyFrom(secret);
    }
    path(name) {
        if (!/^[a-zA-Z0-9._-]+$/.test(name))
            throw new Error("Unsafe encrypted store name");
        return join(this.directory, `${name}.enc`);
    }
    directoryPath() {
        return this.directory;
    }
    async write(name, value) {
        await mkdir(this.directory, { recursive: true, mode: 0o700 });
        const iv = randomBytes(12);
        const cipher = createCipheriv("aes-256-gcm", this.key, iv);
        const plaintext = Buffer.from(JSON.stringify(value, BufferJSON.replacer), "utf8");
        const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
        const payload = Buffer.concat([Buffer.from("BGM1"), iv, cipher.getAuthTag(), encrypted]);
        const target = this.path(name);
        const temporary = `${target}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
        await writeFile(temporary, payload, { mode: 0o600 });
        await rename(temporary, target);
        await chmod(target, 0o600);
    }
    async read(name) {
        const payload = await readFile(this.path(name)).catch((error) => {
            if (error.code === "ENOENT")
                return undefined;
            throw error;
        });
        if (!payload)
            return undefined;
        if (payload.subarray(0, 4).toString("utf8") !== "BGM1")
            throw new Error("Encrypted session format is invalid");
        const iv = payload.subarray(4, 16);
        const tag = payload.subarray(16, 32);
        const decipher = createDecipheriv("aes-256-gcm", this.key, iv);
        decipher.setAuthTag(tag);
        const plaintext = Buffer.concat([decipher.update(payload.subarray(32)), decipher.final()]).toString("utf8");
        return JSON.parse(plaintext, BufferJSON.reviver);
    }
    async remove(name) {
        await rm(this.path(name), { force: true });
    }
}
export async function createEncryptedAuthState(store, sessionId) {
    const storageName = `${sessionId}.auth`;
    const document = await store.read(storageName) ?? { creds: initAuthCreds(), keys: {} };
    let writeQueue = Promise.resolve();
    const persist = () => {
        writeQueue = writeQueue.then(() => store.write(storageName, document));
        return writeQueue;
    };
    const state = {
        creds: document.creds,
        keys: {
            async get(type, ids) {
                const bucket = document.keys[type] ?? {};
                const result = {};
                for (const id of ids) {
                    let value = bucket[id];
                    if (type === "app-state-sync-key" && value) {
                        value = proto.Message.AppStateSyncKeyData.fromObject(value);
                    }
                    if (value)
                        result[id] = value;
                }
                return result;
            },
            async set(data) {
                for (const [type, entries] of Object.entries(data)) {
                    const bucket = document.keys[type] ??= {};
                    for (const [id, value] of Object.entries(entries ?? {})) {
                        if (value === null || value === undefined)
                            delete bucket[id];
                        else
                            bucket[id] = value;
                    }
                }
                await persist();
            },
        },
    };
    await persist();
    return Object.freeze({ state, saveCreds: persist, clear: () => store.remove(storageName) });
}
