/* jslint node:true */

import assert from 'node:assert';
import { HttpError } from '@cloudron/connect-lastmile';
import util from 'node:util';

function BoxError(reason, errorOrMessage, extra = {}) {
    assert.strictEqual(typeof reason, 'string');
    assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string', `string: ${errorOrMessage} type: ${typeof errorOrMessage} json: ${JSON.stringify(errorOrMessage)}`);
    assert(typeof extra === 'object');

    Error.call(this);
    Error.captureStackTrace(this, this.constructor);

    this.name = this.constructor.name;
    this.reason = reason;
    this.details = {};

    if (typeof errorOrMessage === 'string') {
        this.message = errorOrMessage;
    } else if (errorOrMessage instanceof AggregateError) { //
        const messages = errorOrMessage.errors.map(e => e.message);
        this.message = `${errorOrMessage.message} messages: ${messages.join(',')}`;
        this.nestedError = errorOrMessage;
    } else { // error object
        this.message = errorOrMessage.message;
        this.nestedError = errorOrMessage;
    }

    Object.assign(this, extra);
}
util.inherits(BoxError, Error);
BoxError.ACCESS_DENIED = 'Access Denied';
BoxError.ACME_ERROR = 'Acme Error';
BoxError.ADDONS_ERROR = 'Addons Error';
BoxError.ALREADY_EXISTS = 'Already Exists';
BoxError.BAD_FIELD = 'Bad Field';
BoxError.BAD_STATE = 'Bad State';
BoxError.BUSY = 'Busy';
BoxError.CONFLICT = 'Conflict';
BoxError.CRYPTO_ERROR = 'Crypto Error';
BoxError.DATABASE_ERROR = 'Database Error';
BoxError.DNS_ERROR = 'DNS Error';
BoxError.DOCKER_ERROR = 'Docker Error';
BoxError.EXTERNAL_ERROR = 'External Error'; // use this for external API errors
BoxError.FEATURE_DISABLED = 'Feature Disabled';
BoxError.FS_ERROR = 'FileSystem Error';
BoxError.INACTIVE = 'Inactive'; // service/volume/mount
BoxError.INTERNAL_ERROR = 'Internal Error';
BoxError.INVALID_CREDENTIALS = 'Invalid Credentials'; // routes that take totp/password for further verification
BoxError.IPTABLES_ERROR = 'IPTables Error';
BoxError.LICENSE_ERROR = 'License Error'; // billing or subscription expired
BoxError.LOGROTATE_ERROR = 'Logrotate Error';
BoxError.MAIL_ERROR = 'Mail Error';
BoxError.MOUNT_ERROR = 'Mount Error';
BoxError.NETWORK_ERROR = 'Network Error';
BoxError.NGINX_ERROR = 'Nginx Error';
BoxError.NOT_FOUND = 'Not found';
BoxError.NOT_IMPLEMENTED = 'Not implemented';
BoxError.NOT_SIGNED = 'Not Signed';
BoxError.NOT_SUPPORTED = 'Not Supported';
BoxError.OPENSSL_ERROR = 'OpenSSL Error';
BoxError.PLAN_LIMIT = 'Plan Limit';
BoxError.SHELL_ERROR = 'Shell Error'; // exec or spawn cmd failed
BoxError.TASK_ERROR = 'Task Error';
BoxError.TIMEOUT = 'Timeout';
BoxError.TRY_AGAIN = 'Try Again';

BoxError.prototype.toPlainObject = function () {
    return {
        message: this.message,
        reason: this.reason,
        ...this.details
    };
};

// this is a class method for now in case error is not a BoxError
BoxError.toHttpError = function (error) {
    switch (error.reason) {
    case BoxError.BAD_FIELD:
        return new HttpError(400, error);
    case BoxError.LICENSE_ERROR:
    case BoxError.PLAN_LIMIT:
        return new HttpError(402, error);
    case BoxError.NOT_FOUND:
        return new HttpError(404, error);
    case BoxError.FEATURE_DISABLED:
        return new HttpError(405, error);
    case BoxError.ALREADY_EXISTS:
    case BoxError.BAD_STATE:
    case BoxError.CONFLICT:
    case BoxError.NOT_SUPPORTED:
        return new HttpError(409, error);
    case BoxError.INVALID_CREDENTIALS:
        return new HttpError(412, error);
    case BoxError.EXTERNAL_ERROR:
    case BoxError.ACME_ERROR:
    case BoxError.NETWORK_ERROR:
    case BoxError.FS_ERROR:
    case BoxError.MOUNT_ERROR:
    case BoxError.MAIL_ERROR:
    case BoxError.DOCKER_ERROR:
    case BoxError.ADDONS_ERROR:
    case BoxError.IPTABLES_ERROR:
        return new HttpError(424, error);
    case BoxError.DATABASE_ERROR:
    case BoxError.INTERNAL_ERROR:
    default:
        return new HttpError(500, error);
    }
};

export default BoxError;
