"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
const { generateUserMetadataID, getUserMetadataParams, generateUserFlagID, } = require("../../db/utils");
const { InternalTables } = require("../../db/utils");
const { getGlobalUsers, getRawGlobalUser } = require("../../utilities/global");
const { getFullUser } = require("../../utilities/users");
const { isEqual } = require("lodash");
const { BUILTIN_ROLE_IDS } = require("@devlego/backend-core/roles");
const { getDevelopmentAppID, getProdAppIDs, dbExists, } = require("@devlego/backend-core/db");
const { UserStatus } = require("@devlego/backend-core/constants");
const { getAppDB, doInAppContext } = require("@devlego/backend-core/context");
function rawMetadata() {
    return __awaiter(this, void 0, void 0, function* () {
        const db = getAppDB();
        return (yield db.allDocs(getUserMetadataParams(null, {
            include_docs: true,
        }))).rows.map(row => row.doc);
    });
}
function combineMetadataAndUser(user, metadata) {
    // skip users with no access
    if (user.roleId === BUILTIN_ROLE_IDS.PUBLIC) {
        return null;
    }
    delete user._rev;
    const metadataId = generateUserMetadataID(user._id);
    const newDoc = Object.assign(Object.assign({}, user), { _id: metadataId, tableId: InternalTables.USER_METADATA });
    const found = Array.isArray(metadata)
        ? metadata.find(doc => doc._id === metadataId)
        : metadata;
    // copy rev over for the purposes of equality check
    if (found) {
        newDoc._rev = found._rev;
    }
    if (found == null || !isEqual(newDoc, found)) {
        return Object.assign(Object.assign({}, found), newDoc);
    }
    return null;
}
exports.syncGlobalUsers = () => __awaiter(void 0, void 0, void 0, function* () {
    // sync user metadata
    const db = getAppDB();
    const [users, metadata] = yield Promise.all([getGlobalUsers(), rawMetadata()]);
    const toWrite = [];
    for (let user of users) {
        const combined = yield combineMetadataAndUser(user, metadata);
        if (combined) {
            toWrite.push(combined);
        }
    }
    yield db.bulkDocs(toWrite);
});
exports.syncUser = function (ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        let deleting = false, user;
        const userId = ctx.params.id;
        try {
            user = yield getRawGlobalUser(userId);
        }
        catch (err) {
            if (err && err.status === 404) {
                user = {};
                deleting = true;
            }
            else {
                throw err;
            }
        }
        const roles = deleting ? {} : user.roles;
        // remove props which aren't useful to metadata
        delete user.password;
        delete user.forceResetPassword;
        delete user.roles;
        // run through all production appIDs in the users roles
        let prodAppIds;
        // if they are a builder then get all production app IDs
        if ((user.builder && user.builder.global) || deleting) {
            prodAppIds = yield getProdAppIDs();
        }
        else {
            prodAppIds = Object.entries(roles)
                .filter(entry => entry[1] !== BUILTIN_ROLE_IDS.PUBLIC)
                .map(([appId]) => appId);
        }
        for (let prodAppId of prodAppIds) {
            const roleId = roles[prodAppId];
            const devAppId = getDevelopmentAppID(prodAppId);
            for (let appId of [prodAppId, devAppId]) {
                if (!(yield dbExists(appId))) {
                    continue;
                }
                yield doInAppContext(appId, () => __awaiter(this, void 0, void 0, function* () {
                    const db = getAppDB();
                    const metadataId = generateUserMetadataID(userId);
                    let metadata;
                    try {
                        metadata = yield db.get(metadataId);
                    }
                    catch (err) {
                        if (deleting) {
                            return;
                        }
                        metadata = {
                            tableId: InternalTables.USER_METADATA,
                        };
                    }
                    // assign the roleId for the metadata doc
                    if (roleId) {
                        metadata.roleId = roleId;
                    }
                    let combined = !deleting
                        ? combineMetadataAndUser(user, metadata)
                        : Object.assign(Object.assign({}, metadata), { status: UserStatus.INACTIVE, metadata: BUILTIN_ROLE_IDS.PUBLIC });
                    // if its null then there was no updates required
                    if (combined) {
                        yield db.put(combined);
                    }
                }));
            }
        }
        ctx.body = {
            message: "User synced.",
        };
    });
};
exports.fetchMetadata = function (ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        const database = getAppDB();
        const global = yield getGlobalUsers();
        const metadata = yield rawMetadata(database);
        const users = [];
        for (let user of global) {
            // find the metadata that matches up to the global ID
            const info = metadata.find(meta => meta._id.includes(user._id));
            // remove these props, not for the correct DB
            users.push(Object.assign(Object.assign(Object.assign({}, user), info), { tableId: InternalTables.USER_METADATA, 
                // make sure the ID is always a local ID, not a global one
                _id: generateUserMetadataID(user._id) }));
        }
        ctx.body = users;
    });
};
exports.updateSelfMetadata = function (ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        // overwrite the ID with current users
        ctx.request.body._id = ctx.user._id;
        // make sure no stale rev
        delete ctx.request.body._rev;
        // make sure no csrf token
        delete ctx.request.body.csrfToken;
        yield exports.updateMetadata(ctx);
    });
};
exports.updateMetadata = function (ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        const db = getAppDB();
        const user = ctx.request.body;
        // this isn't applicable to the user
        delete user.roles;
        const metadata = Object.assign({ tableId: InternalTables.USER_METADATA }, user);
        ctx.body = yield db.put(metadata);
    });
};
exports.destroyMetadata = function (ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        const db = getAppDB();
        try {
            const dbUser = yield db.get(ctx.params.id);
            yield db.remove(dbUser._id, dbUser._rev);
        }
        catch (err) {
            // error just means the global user has no config in this app
        }
        ctx.body = {
            message: `User metadata ${ctx.params.id} deleted.`,
        };
    });
};
exports.findMetadata = function (ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        ctx.body = yield getFullUser(ctx, ctx.params.id);
    });
};
exports.setFlag = function (ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        const userId = ctx.user._id;
        const { flag, value } = ctx.request.body;
        if (!flag) {
            ctx.throw(400, "Must supply a 'flag' field in request body.");
        }
        const flagDocId = generateUserFlagID(userId);
        const db = getAppDB();
        let doc;
        try {
            doc = yield db.get(flagDocId);
        }
        catch (err) {
            doc = { _id: flagDocId };
        }
        doc[flag] = value || true;
        yield db.put(doc);
        ctx.body = { message: "Flag set successfully" };
    });
};
exports.getFlags = function (ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        const userId = ctx.user._id;
        const docId = generateUserFlagID(userId);
        const db = getAppDB();
        let doc;
        try {
            doc = yield db.get(docId);
        }
        catch (err) {
            doc = { _id: docId };
        }
        ctx.body = doc;
    });
};
