const debug = require('ghost-ignition').debug('api:v3:utils:permissions');
const Promise = require('bluebird');
const _ = require('lodash');
const permissions = require('../../../services/permissions');
const {i18n} = require('../../../lib/common');
const errors = require('@tryghost/errors');

/**
 * @description Handle requests, which need authentication.
 *
 * @param {Object} apiConfig - Docname & method of API ctrl
 * @param {Object} frame
 * @return {Promise}
 */
const nonePublicAuth = (apiConfig, frame) => {
    debug('check admin permissions');

    let singular;
    if (apiConfig.docName.match(/ies$/)) {
        singular = apiConfig.docName.replace(/ies$/, 'y');
    } else {
        singular = apiConfig.docName.replace(/s$/, '');
    }
    let permissionIdentifier = frame.options.id;

    // CASE: Target ctrl can override the identifier. The identifier is the unique identifier of the target resource
    //       e.g. edit a setting -> the key of the setting
    //       e.g. edit a post -> post id from url param
    //       e.g. change user password -> user id inside of the body structure
    if (apiConfig.identifier) {
        permissionIdentifier = apiConfig.identifier(frame);
    }

    let unsafeAttrObject = apiConfig.unsafeAttrs && _.has(frame, `data.[${apiConfig.docName}][0]`) ? _.pick(frame.data[apiConfig.docName][0], apiConfig.unsafeAttrs) : {};

    if (apiConfig.unsafeAttrsObject) {
        unsafeAttrObject = apiConfig.unsafeAttrsObject(frame);
    }

    const permsPromise = permissions.canThis(frame.options.context)[apiConfig.method][singular](permissionIdentifier, unsafeAttrObject);

    return permsPromise.then((result) => {
        /*
         * Allow the permissions function to return a list of excluded attributes.
         * If it does, omit those attrs from the data passed through
         *
         * NOTE: excludedAttrs differ from unsafeAttrs in that they're determined by the model's permissible function,
         * and the attributes are simply excluded rather than throwing a NoPermission exception
         *
         * TODO: This is currently only needed because of the posts model and the contributor role. Once we extend the
         * contributor role to be able to edit existing tags, this concept can be removed.
         */
        if (result && result.excludedAttrs && _.has(frame, `data.[${apiConfig.docName}][0]`)) {
            frame.data[apiConfig.docName][0] = _.omit(frame.data[apiConfig.docName][0], result.excludedAttrs);
        }
    }).catch((err) => {
        if (err instanceof errors.NoPermissionError) {
            err.message = i18n.t('errors.api.utils.noPermissionToCall', {
                method: apiConfig.method,
                docName: apiConfig.docName
            });
            return Promise.reject(err);
        }

        if (errors.utils.isIgnitionError(err)) {
            return Promise.reject(err);
        }

        return Promise.reject(new errors.GhostError({
            err: err
        }));
    });
};

// @TODO: https://github.com/TryGhost/Ghost/issues/10735
module.exports = {
    /**
     * @description Handle permission stage for v3 API.
     *
     * @param {Object} apiConfig - Docname & method of target ctrl.
     * @param {Object} frame
     * @return {Promise}
     */
    handle(apiConfig, frame) {
        debug('handle');

        // @TODO: https://github.com/TryGhost/Ghost/issues/10099
        frame.options.context = permissions.parseContext(frame.options.context);

        // CASE: Content API access
        if (frame.options.context.public) {
            debug('check content permissions');

            // @TODO: Remove when we drop v0.1
            // @TODO: https://github.com/TryGhost/Ghost/issues/10733
            return permissions.applyPublicRules(apiConfig.docName, apiConfig.method, {
                status: frame.options.status,
                id: frame.options.id,
                uuid: frame.options.uuid,
                slug: frame.options.slug,
                data: {
                    status: frame.data.status,
                    id: frame.data.id,
                    uuid: frame.data.uuid,
                    slug: frame.data.slug
                }
            });
        }

        return nonePublicAuth(apiConfig, frame);
    }
};