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


const ARCHIVE_FIELDS = [ 'archives.id', 'backupId', 'archives.creationTime', 'backups.remotePath', 'backups.siteId', 'backups.manifestJson', 'backups.appConfigJson', '(archives.icon IS NOT NULL) AS hasIcon', '(archives.packageIcon IS NOT NULL) AS hasPackageIcon' ];

function postProcess(result) {
    assert.strictEqual(typeof result, 'object');

    result.appConfig = result.appConfigJson ? safe.JSON.parse(result.appConfigJson) : null;
    delete result.appConfigJson;

    result.manifest = result.manifestJson ? safe.JSON.parse(result.manifestJson) : null;
    delete result.manifestJson;

    result.iconUrl = result.hasIcon || result.hasPackageIcon  ? `/api/v1/archives/${result.id}/icon` : null;

    return result;
}

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

    const result = await database.query(`SELECT ${ARCHIVE_FIELDS} FROM archives LEFT JOIN backups ON archives.backupId = backups.id  WHERE archives.id = ? ORDER BY creationTime DESC`, [ id ]);
    if (result.length === 0) return null;

    return postProcess(result[0]);
}

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

    const results = await database.query('SELECT icon, packageIcon FROM archives WHERE id=?', [ id ]);
    if (results.length === 0) return null;
    return { icon: results[0].icon, packageIcon: results[0].packageIcon };
}

async function getIcon(id, options) {
    assert.strictEqual(typeof id, 'string');
    assert.strictEqual(typeof options, 'object');

    const icons = await getIcons(id);
    if (!icons) throw new BoxError(BoxError.NOT_FOUND, 'No such backup');

    if (!options.original && icons.icon) return icons.icon;
    if (icons.packageIcon) return icons.packageIcon;

    return null;
}

async function add(backupId, data, auditSource) {
    assert.strictEqual(typeof backupId, 'string');
    assert.strictEqual(typeof data, 'object');
    assert(auditSource && typeof auditSource === 'object');

    const id = crypto.randomUUID();

    const [error] = await safe(database.query('INSERT INTO archives (id, backupId, icon, packageIcon) VALUES (?, ?, ?, ?)',
        [ id, backupId, data.icon, data.packageIcon ]));

    if (error && error.sqlCode === 'ER_NO_REFERENCED_ROW_2') throw new BoxError(BoxError.NOT_FOUND, 'Backup not found');
    if (error && error.sqlCode === 'ER_DUP_ENTRY') throw new BoxError(BoxError.ALREADY_EXISTS, 'Archive already exists');
    if (error) throw error;

    await eventlog.add(eventlog.ACTION_ARCHIVES_ADD, auditSource, { id, backupId });

    return id;
}

async function list(page, perPage) {
    assert(typeof page === 'number' && page > 0);
    assert(typeof perPage === 'number' && perPage > 0);

    const results = await database.query(`SELECT ${ARCHIVE_FIELDS} FROM archives LEFT JOIN backups ON archives.backupId = backups.id ORDER BY creationTime DESC LIMIT ?,?`, [ (page-1)*perPage, perPage ]);

    results.forEach(function (result) { postProcess(result); });

    return results;
}

async function listBackupIds() {
    const results = await database.query(`SELECT backupId FROM archives`, []);
    return results.map(r => r.backupId);
}

async function del(archive, auditSource) {
    assert.strictEqual(typeof archive, 'object');
    assert(auditSource && typeof auditSource === 'object');

    const result = await database.query('DELETE FROM archives WHERE id=?', [ archive.id ]);
    if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'No such archive');

    await eventlog.add(eventlog.ACTION_ARCHIVES_DEL, auditSource, { id: archive.id, backupId: archive.backupId });
}

export default {
    get,
    getIcons,
    getIcon,
    add,
    list,
    listBackupIds,
    del,
};
