import assert from 'node:assert';
import crypto from 'node:crypto';
import database from './database.js';
import logger from './logger.js';
import mysql from 'mysql2';
import notifications from './notifications.js';
import safe from 'safetydance';

const { log } = logger('eventlog');

const ACTION_ACTIVATE = 'cloudron.activate';
const ACTION_USER_LOGIN = 'user.login';
const ACTION_USER_LOGIN_GHOST = 'user.login.ghost';
const ACTION_USER_LOGOUT = 'user.logout';


const EVENTLOG_FIELDS = [ 'id', 'action', 'sourceJson', 'dataJson', 'creationTime' ].join(',');

function postProcess(record) {
    // usually we have sourceJson and dataJson, however since this used to be the JSON data type, we don't
    record.source = safe.JSON.parse(record.sourceJson);
    delete record.sourceJson;
    record.data = safe.JSON.parse(record.dataJson);
    delete record.dataJson;

    return record;
}

// never throws, only logs because previously code did not take a callback
async function add(action, source, data) {
    assert.strictEqual(typeof action, 'string');
    assert.strictEqual(typeof source, 'object'); // an AuditSource
    assert.strictEqual(typeof data, 'object');

    const id = crypto.randomUUID();
    await database.query('INSERT INTO eventlog (id, action, sourceJson, dataJson) VALUES (?, ?, ?, ?)', [ id, action, JSON.stringify(source), JSON.stringify(data) ]);
    await notifications.onEvent(id, action, source, data);
    return id;
}

// never throws, only logs because previously code did not take a callback
async function upsertLoginEvent(action, source, data) {
    assert.strictEqual(typeof action, 'string');
    assert.strictEqual(typeof source, 'object'); // an AuditSource
    assert.strictEqual(typeof data, 'object');

    // can't do a real sql upsert, for frequent eventlog entries we only have to do 2 queries once a day
    const queries = [{
        query: 'UPDATE eventlog SET creationTime=NOW(), dataJson=? WHERE action = ? AND sourceJson LIKE ? AND DATE(creationTime)=CURDATE()',
        args: [ JSON.stringify(data), action, JSON.stringify(source) ]
    }, {
        query: 'SELECT ' + EVENTLOG_FIELDS + ' FROM eventlog WHERE action = ? AND sourceJson LIKE ? AND DATE(creationTime)=CURDATE()',
        args: [ action, JSON.stringify(source) ]
    }];

    const result = await database.transaction(queries);
    if (result[0].affectedRows >= 1) return result[1][0].id;

    // no existing eventlog found, create one
    return await add(action, source, data);
}

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

    const result = await database.query(`SELECT ${EVENTLOG_FIELDS} FROM eventlog WHERE id = ?`, [ id ]);
    if (result.length === 0) return null;

    return postProcess(result[0]);
}

async function getActivationEvent() {
    const result = await database.query(`SELECT ${EVENTLOG_FIELDS} FROM eventlog WHERE action = ? ORDER BY creationTime`, [ ACTION_ACTIVATE ]);
    if (result.length === 0) return null;

    return postProcess(result[0]);
}

async function listPaged(filter, page, perPage) {
    const { actions, search, from = null, to = null } = filter;

    assert(Array.isArray(actions));
    assert(typeof search === 'string' || search === null);
    assert(from === null || from instanceof Date, 'from must be a Date or null');
    assert(to === null || to instanceof Date, 'to must be a Date or null');
    assert.strictEqual(typeof page, 'number');
    assert.strictEqual(typeof perPage, 'number');

    const conditions = [], data = [];

    if (search) conditions.push('(sourceJson LIKE ' + mysql.escape('%' + search + '%') + ' OR dataJson LIKE ' + mysql.escape('%' + search + '%') + ')');

    if (actions.length) {
        const actionConds = actions.map((a) => ' (action LIKE ' + mysql.escape(`%${a}%`) + ') ').join(' OR ');
        conditions.push('( ' + actionConds + ' )');
    }
    if (from) {
        conditions.push('creationTime >= ?');
        data.push(from);
    }
    if (to) {
        conditions.push('creationTime <= ?');
        data.push(to);
    }

    let query = `SELECT ${EVENTLOG_FIELDS} FROM eventlog`;
    if (conditions.length) query += ' WHERE ' + conditions.join(' AND ');
    query += ' ORDER BY creationTime DESC LIMIT ?,?';

    data.push((page - 1) * perPage);
    data.push(perPage);

    const results = await database.query(query, data);
    results.forEach(postProcess);
    return results;
}

async function cleanup(options) {
    assert.strictEqual(typeof options, 'object');

    const creationTime = options.creationTime;
    log(`cleanup: pruning events. creationTime: ${creationTime.toString()}`);

    // only these actions are pruned
    const actions = [
        ACTION_USER_LOGIN,
        ACTION_USER_LOGIN_GHOST,
        ACTION_USER_LOGOUT,
    ];

    let query = `SELECT ${EVENTLOG_FIELDS} FROM eventlog WHERE creationTime <= ? AND (`;
    const data = [ creationTime ];
    actions.forEach(function (action, i) {
        query += ' action = ? ';
        data.push(action);
        if (i < actions.length-1) query += ' OR ';
    });
    query += ' ) ';

    const results = await database.query(query, data);

    for (const result of results) {
        await database.query('DELETE FROM notifications WHERE eventId=?', [ result.id ]); // remove notifications that reference the events as well
        await database.query('DELETE FROM eventlog WHERE id=?', [ result.id ]);
    }
}

async function clear() {
    await database.query('DELETE FROM eventlog');
}

export default {
    add,
    upsertLoginEvent,
    get,
    getActivationEvent,
    listPaged,
    cleanup,
    _clear: clear,

    // keep in sync with webadmin index.js filter
    ACTION_ACTIVATE,
    ACTION_APP_CLONE: 'app.clone',
    ACTION_APP_CONFIGURE: 'app.configure',
    ACTION_APP_REPAIR: 'app.repair',
    ACTION_APP_INSTALL: 'app.install',
    ACTION_APP_RESTORE: 'app.restore',
    ACTION_APP_IMPORT: 'app.import',
    ACTION_APP_UNINSTALL: 'app.uninstall',
    ACTION_APP_UPDATE: 'app.update',
    ACTION_APP_UPDATE_FINISH: 'app.update.finish',
    ACTION_APP_BACKUP: 'app.backup',
    ACTION_APP_BACKUP_FINISH: 'app.backup.finish',
    ACTION_APP_LOGIN: 'app.login',
    ACTION_APP_OOM: 'app.oom',
    ACTION_APP_UP: 'app.up',
    ACTION_APP_DOWN: 'app.down',
    ACTION_APP_START: 'app.start',
    ACTION_APP_STOP: 'app.stop',
    ACTION_APP_RESTART: 'app.restart',

    ACTION_ARCHIVES_ADD: 'archives.add',
    ACTION_ARCHIVES_DEL: 'archives.del',

    ACTION_BACKUP_FINISH: 'backup.finish',
    ACTION_BACKUP_START: 'backup.start',
    ACTION_BACKUP_CLEANUP_START: 'backup.cleanup.start', // obsolete
    ACTION_BACKUP_CLEANUP_FINISH: 'backup.cleanup.finish',

    ACTION_BACKUP_INTEGRITY_START: 'backup.integrity.start',
    ACTION_BACKUP_INTEGRITY_FINISH: 'backup.integrity.finish',

    ACTION_BACKUP_SITE_ADD: 'backupsite.add',
    ACTION_BACKUP_SITE_REMOVE: 'backupsite.remove',
    ACTION_BACKUP_SITE_UPDATE: 'backupsite.update',

    ACTION_BRANDING_NAME: 'branding.name',
    ACTION_BRANDING_FOOTER: 'branding.footer',
    ACTION_BRANDING_AVATAR: 'branding.avatar',

    ACTION_CERTIFICATE_NEW: 'certificate.new',
    ACTION_CERTIFICATE_RENEWAL: 'certificate.renew', // obsolete
    ACTION_CERTIFICATE_CLEANUP: 'certificate.cleanup',

    ACTION_DASHBOARD_DOMAIN_UPDATE: 'dashboard.domain.update',

    ACTION_DIRECTORY_SERVER_CONFIGURE: 'directoryserver.configure',

    ACTION_DOMAIN_ADD: 'domain.add',
    ACTION_DOMAIN_UPDATE: 'domain.update',
    ACTION_DOMAIN_REMOVE: 'domain.remove',

    ACTION_EXTERNAL_LDAP_CONFIGURE: 'externalldap.configure',

    ACTION_GROUP_ADD: 'group.add',
    ACTION_GROUP_REMOVE: 'group.remove',
    ACTION_GROUP_UPDATE: 'group.update',
    ACTION_GROUP_MEMBERSHIP: 'group.membership',

    ACTION_INSTALL_FINISH: 'cloudron.install.finish',

    ACTION_MAIL_LOCATION: 'mail.location',
    ACTION_MAIL_ENABLED: 'mail.enabled',
    ACTION_MAIL_DISABLED: 'mail.disabled',
    ACTION_MAIL_MAILBOX_ADD: 'mail.box.add',
    ACTION_MAIL_MAILBOX_REMOVE: 'mail.box.remove',
    ACTION_MAIL_MAILBOX_UPDATE: 'mail.box.update',
    ACTION_MAIL_LIST_ADD: 'mail.list.add',
    ACTION_MAIL_LIST_REMOVE: 'mail.list.remove',
    ACTION_MAIL_LIST_UPDATE: 'mail.list.update',

    ACTION_PROVISION: 'cloudron.provision',
    ACTION_RESTORE: 'cloudron.restore', // unused
    ACTION_START: 'cloudron.start',

    ACTION_REGISTRY_ADD: 'registry.add',
    ACTION_REGISTRY_UPDATE: 'registry.update',
    ACTION_REGISTRY_DEL: 'registry.del',

    ACTION_SERVICE_CONFIGURE: 'service.configure',
    ACTION_SERVICE_REBUILD: 'service.rebuild',
    ACTION_SERVICE_RESTART: 'service.restart',

    ACTION_UPDATE: 'cloudron.update',
    ACTION_UPDATE_FINISH: 'cloudron.update.finish',

    ACTION_USER_ADD: 'user.add',
    ACTION_USER_LOGIN,
    ACTION_USER_LOGIN_GHOST,
    ACTION_USER_LOGOUT,
    ACTION_USER_REMOVE: 'user.remove',
    ACTION_USER_UPDATE: 'user.update',
    ACTION_USER_TRANSFER: 'user.transfer',

    ACTION_USER_DIRECTORY_PROFILE_CONFIG_UPDATE: 'userdirectory.profileconfig.update',

    ACTION_VOLUME_ADD: 'volume.add',
    ACTION_VOLUME_UPDATE: 'volume.update',
    ACTION_VOLUME_REMOUNT: 'volume.remount',
    ACTION_VOLUME_REMOVE: 'volume.remove',

    ACTION_DYNDNS_UPDATE: 'dyndns.update',

    ACTION_SUPPORT_TICKET: 'support.ticket',
    ACTION_SUPPORT_SSH: 'support.ssh',

    ACTION_PROCESS_CRASH: 'system.crash' // obsolete
};
