import assert from 'node:assert';
import AuditSource from '../auditsource.js';
import backups from '../backups.js';
import backupSites from '../backupsites.js';
import BoxError from '../boxerror.js';
import { HttpError } from '@cloudron/connect-lastmile';
import { HttpSuccess } from '@cloudron/connect-lastmile';
import safe from 'safetydance';


async function load(req, res, next) {
    assert.strictEqual(typeof req.params.id, 'string');

    const [error, result] = await safe(backupSites.get(req.params.id));
    if (error) return next(BoxError.toHttpError(error));
    if (!result) return next(new HttpError(404, 'Backup site not found'));

    req.resources.backupSite = result;

    next();
}

async function get(req, res, next) {
    assert.strictEqual(typeof req.params.id, 'string');

    next(new HttpSuccess(200,  backupSites.removePrivateFields(req.resources.backupSite)));
}

async function list(req, res, next) {
    const [error, result] = await safe(backupSites.list());
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, { backupSites: result.map(backupSites.removePrivateFields) }));
}

async function add(req, res, next) {
    assert.strictEqual(typeof req.body, 'object');

    const { name, format, provider, contents, config, schedule, retention } = req.body;

    if (typeof format !== 'string') return next(new HttpError(400, 'format must be a string'));
    if (typeof name !== 'string') return next(new HttpError(400, 'name must be a string'));
    if (typeof provider !== 'string') return next(new HttpError(400, 'provider is required'));
    if (typeof contents !== 'object')  return next(new HttpError(400, 'contents is required'));

    // provider specific options are validated by provider backends
    if (!config || typeof config !== 'object') return next(new HttpError(400, 'config is required'));

    if (typeof schedule !== 'string') return next(new HttpError(400, 'schedule is required'));
    if (!retention || typeof retention !== 'object') return next(new HttpError(400, 'retention is required'));

    if ('limits' in req.body && typeof req.body.limits !== 'object') return next(new HttpError(400, 'limits must be an object'));

    if ('encryptionPassword' in req.body && typeof req.body.encryptionPassword !== 'string') return next(new HttpError(400, 'encryptionPassword must be a string'));
    if ('encryptionPasswordHint' in req.body && typeof req.body.encryptionPasswordHint !== 'string') return next(new HttpError(400, 'encryptionPasswordHint must be a string'));
    if ('encryptedFilenames' in req.body && typeof req.body.encryptedFilenames !== 'boolean') return next(new HttpError(400, 'encryptedFilenames must be a boolean'));

    if ('enableForUpdates' in req.body && typeof req.body.enableForUpdates !== 'boolean') return next(new HttpError(400, 'enableForUpdates must be a boolean'));

    // testing the backup using put/del takes a bit of time at times
    req.clearTimeout();

    const [error, id] = await safe(backupSites.add(req.body, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, { id }));
}

async function del(req, res, next) {
    assert.strictEqual(typeof req.params.id, 'string');
    assert.strictEqual(typeof req.resources.backupSite, 'object');

    const [error] = await safe(backupSites.del(req.resources.backupSite, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(204));
}

async function setLimits(req, res, next) {
    assert.strictEqual(typeof req.body, 'object');

    const { limits } = req.body;

    if (!limits || typeof limits !== 'object') return next(new HttpError(400, 'limits is required'));

    if ('syncConcurrency' in limits) {
        if (typeof limits.syncConcurrency !== 'number') return next(new HttpError(400, 'syncConcurrency must be a positive integer'));
        if (limits.syncConcurrency < 1) return next(new HttpError(400, 'syncConcurrency must be a positive integer'));
    }
    if ('copyConcurrency' in limits) {
        if (typeof limits.copyConcurrency !== 'number') return next(new HttpError(400, 'copyConcurrency must be a positive integer'));
        if (limits.copyConcurrency < 1) return next(new HttpError(400, 'copyConcurrency must be a positive integer'));
    }
    if ('downloadConcurrency' in limits) {
        if (typeof limits.downloadConcurrency !== 'number') return next(new HttpError(400, 'downloadConcurrency must be a positive integer'));
        if (limits.downloadConcurrency < 1) return next(new HttpError(400, 'downloadConcurrency must be a positive integer'));
    }
    if ('deleteConcurrency' in limits) {
        if (typeof limits.deleteConcurrency !== 'number') return next(new HttpError(400, 'deleteConcurrency must be a positive integer'));
        if (limits.deleteConcurrency < 1) return next(new HttpError(400, 'deleteConcurrency must be a positive integer'));
    }
    if ('uploadPartSize' in limits) {
        if (typeof limits.uploadPartSize !== 'number') return next(new HttpError(400, 'uploadPartSize must be a positive integer'));
        if (limits.uploadPartSize < 1) return next(new HttpError(400, 'uploadPartSize must be a positive integer'));
    }

    if ('memoryLimit' in limits && typeof limits.memoryLimit !== 'number') return next(new HttpError(400, 'memoryLimit must be a positive integer'));

    const [error] = await safe(backupSites.setLimits(req.resources.backupSite, limits, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, {}));
}

async function setConfig(req, res, next) {
    assert.strictEqual(typeof req.body, 'object');

    if (!req.body.config || typeof req.body.config !== 'object') return next(new HttpError(400, 'config is required'));

    // testing the backup using put/del takes a bit of time at times
    req.clearTimeout();

    const [error] = await safe(backupSites.setConfig(req.resources.backupSite, req.body.config, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, {}));
}

async function setSchedule(req, res, next) {
    assert.strictEqual(typeof req.body, 'object');

    if (typeof req.body.schedule !== 'string') return next(new HttpError(400, 'schedule is required'));

    const [error] = await safe(backupSites.setSchedule(req.resources.backupSite, req.body.schedule, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, {}));
}

async function setRetention(req, res, next) {
    assert.strictEqual(typeof req.body, 'object');

    if (!req.body.retention || typeof req.body.retention !== 'object') return next(new HttpError(400, 'retention is required'));

    const [error] = await safe(backupSites.setRetention(req.resources.backupSite, req.body.retention, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, {}));
}

async function setEnabledForUpdates(req, res, next) {
    assert.strictEqual(typeof req.body, 'object');

    if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enable is required'));

    const [error] = await safe(backupSites.setEnabledForUpdates(req.resources.backupSite, req.body.enable, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, {}));
}

async function setContents(req, res, next) {
    assert.strictEqual(typeof req.body, 'object');

    if (typeof req.body.contents !== 'object') return next(new HttpError(400, 'contents must be an object'));

    const [error] = await safe(backupSites.setContents(req.resources.backupSite, req.body.contents, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, {}));
}

async function setEncryption(req, res, next) {
    assert.strictEqual(typeof req.body, 'object');

    if ('encryptionPassword' in req.body) {
        if (req.body.encryptionPassword === null || typeof req.body.encryptionPassword !== 'string') return next(new HttpError(400, 'encryptionPassword must be a string or null'));
    }
    if ('encryptionPasswordHint' in req.body && typeof req.body.encryptionPasswordHint !== 'string') return next(new HttpError(400, 'encryptionPasswordHint must be a string'));
    if ('encryptedFilenames' in req.body && typeof req.body.encryptedFilenames !== 'boolean') return next(new HttpError(400, 'encryptedFilenames must be a boolean'));

    const [error] = await safe(backupSites.setEncryption(req.resources.backupSite, req.body, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, {}));
}

async function setName(req, res, next) {
    assert.strictEqual(typeof req.body, 'object');

    if (typeof req.body.name !== 'string') return next(new HttpError(400, 'name is required'));

    const [error] = await safe(backupSites.setName(req.resources.backupSite, req.body.name, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, {}));
}

async function createBackup(req, res, next) {
    assert.strictEqual(typeof req.resources.backupSite, 'object');

    const [error, taskId] = await safe(backupSites.startBackupTask(req.resources.backupSite, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(202, { taskId }));
}

async function cleanup(req, res, next) {
    assert.strictEqual(typeof req.resources.backupSite, 'object');

    const [error, taskId] = await safe(backupSites.startCleanupTask(req.resources.backupSite, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(202, { taskId }));
}

async function remount(req, res, next) {
    assert.strictEqual(typeof req.resources.backupSite, 'object');

    const [error] = await safe(backupSites.remount(req.resources.backupSite));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(202, {}));
}

async function getStatus(req, res, next) {
    assert.strictEqual(typeof req.resources.backupSite, 'object');

    const [error, mountStatus] = await safe(backupSites.getStatus(req.resources.backupSite));
    if (error) return next(BoxError.toHttpError(error));
    next(new HttpSuccess(200, mountStatus));
}

async function listBackups(req, res, next) {
    assert.strictEqual(typeof req.resources.backupSite, 'object');

    const page = typeof req.query.page === 'string' ? parseInt(req.query.page) : 1;
    if (!page || page < 0) return next(new HttpError(400, 'page query param has to be a postive number'));

    const perPage = typeof req.query.per_page === 'string'? parseInt(req.query.per_page) : 25;
    if (!perPage || perPage < 0) return next(new HttpError(400, 'per_page query param has to be a postive number'));

    const [error, results] = await safe(backups.listByTypePaged(backups.BACKUP_TYPE_BOX, req.resources.backupSite.id, page, perPage));
    if (error) return next(BoxError.toHttpError(error));
    next(new HttpSuccess(200, { backups: results.map(backups.removePrivateFields) }));
}

export default {
    load,

    list,
    get,
    add,
    del,

    // separate update routes to skip (slow) storage validation
    setConfig,
    setLimits,
    setSchedule,
    setRetention,
    setEnabledForUpdates,
    setName,
    setContents,
    setEncryption,

    listBackups,
    createBackup,
    cleanup,
    remount,
    getStatus,
};
