async function deriveKeyFromPassword(password, salt = null, iter = 100000) {
    if (!salt) {
        salt = window.crypto.getRandomValues(new Uint8Array(16))
    }
    const pwbuffer = await window.crypto.subtle.importKey(
        "raw",
        new TextEncoder().encode(password),
        {name: "PBKDF2"},
        false,
        ["deriveBits"]
    );
    const key_bits = await window.crypto.subtle.deriveBits(
        {
            name: "PBKDF2",
            salt,
            iterations: iter,
            hash: "SHA-256"
        },
        pwbuffer,
        256
    )
    const key = await window.crypto.subtle.importKey(
        "raw",
        key_bits,
        {name: "AES-GCM"},
        false,
        ["encrypt", "decrypt"]
    )
    return {key, salt, alg: 'pbkdf2', iter}
}

async function serializeIdentityRecord(user, keypair, password = null) {
    if (password) {
        const plain_key = `${nacl.to_hex(keypair.signSk).slice(0, 64)}:${nacl.to_hex(keypair.signPk).slice(56, 64)}`
        const {key, salt, alg, iter} = await deriveKeyFromPassword(password)
        const iv = window.crypto.getRandomValues(new Uint8Array(12))
        const encrypted_key = await window.crypto.subtle.encrypt(
            {
                name: "AES-GCM",
                iv
            },
            key,
            new TextEncoder('utf-8').encode(plain_key)
        )
        return `${user}:$${alg}$${iter}$${nacl.to_hex(salt)}$${nacl.to_hex(iv)}$${nacl.to_hex(new Uint8Array(encrypted_key))}`
    }
    return `${user}:${nacl.to_hex(keypair.signSk).slice(0, 64)}:${nacl.to_hex(keypair.signPk).slice(56, 64)}`
}

function parseIdentityRecord(record) {
    const segments = record.split(':');
    if (segments.length === 3) {
        try {
            const [user, key_hex, sign_hex] = segments
            const keypair = nacl.crypto_sign_keypair_from_seed(nacl.from_hex(key_hex))
            const [username, domain] = user.split('@')
            if (nacl.to_hex(keypair.signPk).slice(56, 64) !== sign_hex || username === '' || domain === '') {
                return {user: null, keypair: null, errors: ['Invalid identity record']}
            }
            return {user, keypair, errors: null}
        } catch (e) {
            return {user: null, keypair: null, errors: ['Invalid identity record']}
        }
    } else if (segments.length === 2) {
        try {
            const [user, rest] = segments
            if (!rest.startsWith('$')) {
                return {user: null, keypair: null, errors: ['Invalid identity record']}
            }
            const [alg, iter, salt, iv, crypted] = rest.slice(1).split('$')
            if (alg !== 'pbkdf2') {
                return {user: null, keypair: null, errors: ['Invalid identity record']}
            }
            return {
                user,
                alg,
                iter: parseInt(iter),
                salt: nacl.from_hex(salt),
                iv: nacl.from_hex(iv),
                crypted: nacl.from_hex(crypted),
                errors: null
            }
        } catch (e) {
            return {user: null, keypair: null, errors: ['Invalid identity record']}
        }
    }
    return {user: null, keypair: null, errors: ['Invalid identity record']}
}

async function decryptIdentityRecord(meta, user, password) {
    try {
        const {alg, iter, salt, iv, crypted} = meta
        if (alg !== 'pbkdf2') {
            return {user: null, keypair: null, errors: ['Unsupported algorithm']}
        }
        const {key} = await deriveKeyFromPassword(password, salt, iter)
        const decrypted = await window.crypto.subtle.decrypt(
            {
                name: "AES-GCM",
                iv
            },
            key,
            crypted
        )
        const plain_key = new TextDecoder('utf-8').decode(decrypted)
        return parseIdentityRecord(`${user}:${plain_key}`)
    }catch (e) {
        return {user: null, keypair: null, errors: ['could not decrypt identity record']}
    }
}

export {parseIdentityRecord, serializeIdentityRecord, decryptIdentityRecord}