import { getReflection, getHeaders, setRefresh, tryCompress, ALG } from '/assets/{{ version }}/js/core.js';

async function hash(
    app_name,
    account,
    password,
) {
    const txt_encoder = new TextEncoder();

    const encoded_app_name = txt_encoder.encode(app_name);
    const encoded_account = txt_encoder.encode(account);
    const encoded_password = txt_encoder.encode(password);

    const all = new Uint8Array([...encoded_app_name, ...encoded_account, ...encoded_password]);

    const reflection = await getReflection();

    const alg = ALG[reflection.auth.password.client_hash]
    const hash = await crypto.subtle.digest(alg, all);

    return [encoded_account, new Uint8Array(hash)];
}

export async function register(
    account,
    password,
    app_name,
) {
    const [encoded_account, password_hash] = await hash(app_name, account, password);
    if (encoded_account.length > 255) throw new Error('encoded account cannot be longer than 255 bytes');

    const keypair = await crypto.subtle.generateKey(
        {
            name: 'X25519',
        },
        true,
        ["deriveKey"],
    );

    const pubKeyBytes = new Uint8Array(
        await crypto.subtle.exportKey("raw", keypair.publicKey),
    );

    const body = new Uint8Array([
        encoded_account.length,
        ...encoded_account,
        ...password_hash,
        ...pubKeyBytes,
    ]);

    const res = await fetch('/register', {
        method: 'PUT',
        body,
        headers: getHeaders(),
    });

    const res_bytes = await res.bytes();

    const ephemeralPubKey = await crypto.subtle.importKey(
        'raw',
        res_bytes.slice(0, 32),
        {
            name: 'X25519',
        },
        false,
        []
    );

    const nonce = res_bytes.slice(32, 44);
    const ciphertext = res_bytes.slice(44);

    const sharedSecret = await crypto.subtle.deriveKey(
        {
            name: 'X25519',
            public: ephemeralPubKey,
        },
        keypair.privateKey,
        {
            name: 'AES-GCM',
            length: 256,
        },
        false,
        ["decrypt"],
    );

    const decrypted = await crypto.subtle.decrypt(
        { name: 'AES-GCM', iv: nonce },
        sharedSecret,
        ciphertext,
    );

    const decoder = new TextDecoder();
    return decoder.decode(decrypted);
}

export async function login(
    account,
    password,
    mfa_code,
    app_name,
    get_cookies,
) {
    const [encoded_account, password_hash] = await hash(app_name, account, password);
    if (encoded_account.length > 255) throw new Error('encoded account cannot be longer than 255 bytes');

    const txt_encoder = new TextEncoder();
    const encoded_mfa = txt_encoder.encode(mfa_code);

    const body = new Uint8Array([encoded_account.length, ...encoded_account, ...encoded_mfa, ...password_hash]);

    const res = await fetch('/login', {
        method: 'PUT',
        body,
        credentials: get_cookies ? 'include' : 'omit',
        headers: getHeaders({ cookies: get_cookies }),
    });

    const refresh_token = await res.bytes();

    await setRefresh(refresh_token);

    return refresh_token;
}

export async function rest(
    method,
    path,
    authorization = false,
    object = null,
) {
    if (object && method != 'GET') {
        const { body, headers } = await tryCompress(
            JSON.stringify(object),
            'application/json'
        );

        return fetch(path, {
            method,
            body,
            headers: {
                ...headers,
                ...getHeaders({ authorization }),
            },
        })
    } else {
        return fetch(path, {
            method,
            headers: {
                ...getHeaders({ authorization }),
            },
        })
    }
}