import assert from 'node:assert';
import BoxError from './boxerror.js';
import crypto from 'node:crypto';
import database from './database.js';
import hat from './hat.js';
import safe from 'safetydance';
import _ from './underscore.js';


const APP_PASSWORD_FIELDS = [ 'id', 'name', 'userId', 'identifier', 'hashedPassword', 'creationTime', 'expiresAt' ].join(',');

function validateAppPasswordName(name) {
    assert.strictEqual(typeof name, 'string');

    if (name.length < 1) return new BoxError(BoxError.BAD_FIELD, 'name must be atleast 1 char');
    if (name.length >= 200) return new BoxError(BoxError.BAD_FIELD, 'name too long');

    return null;
}

function removePrivateFields(appPassword) {
    return _.pick(appPassword, ['id', 'name', 'userId', 'identifier', 'creationTime', 'expiresAt']);
}

async function get(id) {
    assert.strictEqual(typeof id, 'string');

    const result = await database.query('SELECT ' + APP_PASSWORD_FIELDS + ' FROM appPasswords WHERE id = ?', [ id ]);
    if (result.length === 0) return null;
    return result[0];
}

async function add(userId, identifier, name, expiresAt) {
    assert.strictEqual(typeof userId, 'string');
    assert.strictEqual(typeof identifier, 'string');
    assert.strictEqual(typeof name, 'string');
    assert(expiresAt === null || typeof expiresAt === 'string');

    let error = validateAppPasswordName(name);
    if (error) throw error;

    if (identifier.length < 1) throw new BoxError(BoxError.BAD_FIELD, 'identifier must be atleast 1 char');

    const password = hat(16 * 4);
    const hashedPassword = crypto.createHash('sha256').update(password).digest('base64');

    const appPassword = {
        id: 'uid-' + crypto.randomUUID(),
        name,
        userId,
        identifier,
        password,
        hashedPassword,
        expiresAt
    };

    const query = 'INSERT INTO appPasswords (id, userId, identifier, name, hashedPassword, expiresAt) VALUES (?, ?, ?, ?, ?, ?)';
    const args = [ appPassword.id, appPassword.userId, appPassword.identifier, appPassword.name, appPassword.hashedPassword, appPassword.expiresAt ? new Date(appPassword.expiresAt) : null ];

    [error] = await safe(database.query(query, args));
    if (error && error.sqlCode === 'ER_DUP_ENTRY' && error.sqlMessage.indexOf('appPasswords_name_userId_identifier') !== -1) throw new BoxError(BoxError.ALREADY_EXISTS, 'name/app combination already exists');
    if (error && error.sqlCode === 'ER_NO_REFERENCED_ROW_2' && error.sqlMessage.indexOf('userId')) throw new BoxError(BoxError.NOT_FOUND, 'user not found');
    if (error) throw error;

    return { id: appPassword.id, password: appPassword.password };
}

async function list(userId) {
    assert.strictEqual(typeof userId, 'string');

    return await database.query('SELECT ' + APP_PASSWORD_FIELDS + ' FROM appPasswords WHERE userId = ? ORDER BY name', [ userId ]);
}

async function del(id) {
    assert.strictEqual(typeof id, 'string');

    const result = await database.query('DELETE FROM appPasswords WHERE id = ?', [ id ]);
    if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'password not found');
}

export default {
    get,
    add,
    list,
    del,

    removePrivateFields
};
