var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
import { Arg, Ctx, Field, InputType, Mutation, ObjectType, registerEnumType } from 'type-graphql';
import { LogError, Metadata, RunView } from '@memberjunction/core';
import { UUIDsEqual } from '@memberjunction/global';
import { RequireSystemUser } from '../directives/RequireSystemUser.js';
import { UserCache } from '@memberjunction/sqlserver-dataprovider';
let SyncRolesAndUsersResultType = class SyncRolesAndUsersResultType {
};
__decorate([
    Field(() => Boolean),
    __metadata("design:type", Boolean)
], SyncRolesAndUsersResultType.prototype, "Success", void 0);
SyncRolesAndUsersResultType = __decorate([
    ObjectType()
], SyncRolesAndUsersResultType);
export { SyncRolesAndUsersResultType };
let RoleInputType = class RoleInputType {
};
__decorate([
    Field(() => String),
    __metadata("design:type", String)
], RoleInputType.prototype, "ID", void 0);
__decorate([
    Field(() => String),
    __metadata("design:type", String)
], RoleInputType.prototype, "Name", void 0);
__decorate([
    Field(() => String, { nullable: true }),
    __metadata("design:type", String)
], RoleInputType.prototype, "Description", void 0);
RoleInputType = __decorate([
    InputType()
], RoleInputType);
export { RoleInputType };
export var UserType;
(function (UserType) {
    UserType["Owner"] = "Owner";
    UserType["User"] = "User";
})(UserType || (UserType = {}));
registerEnumType(UserType, {
    name: "UserType", // GraphQL Enum Name
    description: "Defines whether a user is an Owner or a User",
});
let UserInputType = class UserInputType {
};
__decorate([
    Field(() => String),
    __metadata("design:type", String)
], UserInputType.prototype, "ID", void 0);
__decorate([
    Field(() => String),
    __metadata("design:type", String)
], UserInputType.prototype, "Name", void 0);
__decorate([
    Field(() => String),
    __metadata("design:type", String)
], UserInputType.prototype, "Email", void 0);
__decorate([
    Field(() => UserType),
    __metadata("design:type", String)
], UserInputType.prototype, "Type", void 0);
__decorate([
    Field(() => String, { nullable: true }),
    __metadata("design:type", String)
], UserInputType.prototype, "FirstName", void 0);
__decorate([
    Field(() => String, { nullable: true }),
    __metadata("design:type", String)
], UserInputType.prototype, "LastName", void 0);
__decorate([
    Field(() => String, { nullable: true }),
    __metadata("design:type", String)
], UserInputType.prototype, "Title", void 0);
__decorate([
    Field(() => [RoleInputType], { nullable: true }),
    __metadata("design:type", Array)
], UserInputType.prototype, "Roles", void 0);
UserInputType = __decorate([
    InputType()
], UserInputType);
export { UserInputType };
let RolesAndUsersInputType = class RolesAndUsersInputType {
};
__decorate([
    Field(() => [UserInputType]),
    __metadata("design:type", Array)
], RolesAndUsersInputType.prototype, "Users", void 0);
__decorate([
    Field(() => [RoleInputType]),
    __metadata("design:type", Array)
], RolesAndUsersInputType.prototype, "Roles", void 0);
RolesAndUsersInputType = __decorate([
    InputType()
], RolesAndUsersInputType);
export { RolesAndUsersInputType };
export class SyncRolesAndUsersResolver {
    /**
     * This mutation will sync both the roles and the users, and the user/role relationships in the system with the data provided in the input.
     * Roles are matched by the name (case insensitive) and users are matched by email
     * @param data
     */
    async SyncRolesAndUsers(data, context) {
        try {
            // first we sync the roles, then the users 
            const roleResult = await this.SyncRoles(data.Roles, context);
            if (roleResult?.Success) {
                const usersResult = await this.SyncUsers(data.Users, context);
                if (usersResult?.Success) {
                    // refresh the user cache, don't set an auto-refresh
                    // interval here becuase that is alreayd done at startup
                    // and will keep going on its own as per the config. This is a
                    // special one-time refresh since we made changes here.
                    await UserCache.Instance.Refresh(context.dataSource);
                }
                return usersResult;
            }
            else {
                return roleResult;
            }
        }
        catch (err) {
            LogError(err);
            throw new Error('Error syncing roles and users\n\n' + err);
        }
    }
    /**
     * This mutation will sync the roles in the system with the data provided in the input, using the role name for matching (case insensitive)
     * @param data
     */
    async SyncRoles(roles, context) {
        try {
            // we iterate through the provided roles and we remove roles that are not in the input and add roles that are new
            // and update roles that already exist
            const rv = new RunView();
            const result = await rv.RunView({
                EntityName: "MJ: Roles",
                ResultType: 'entity_object'
            }, context.userPayload.userRecord);
            if (result && result.Success) {
                const currentRoles = result.Results;
                if (await this.DeleteRemovedRoles(currentRoles, roles, context.userPayload.userRecord, context.userPayload)) {
                    if (await this.AddNewRoles(currentRoles, roles, context.userPayload.userRecord, context.userPayload)) {
                        return await this.UpdateExistingRoles(currentRoles, roles, context.userPayload);
                    }
                }
            }
            return { Success: false }; // if we get here, something went wrong
        }
        catch (err) {
            LogError(err);
            throw new Error('Error syncing roles and users\n\n' + err);
        }
    }
    async UpdateExistingRoles(currentRoles, futureRoles, userPayload) {
        // go through the future roles and update any that are in the current roles
        const md = new Metadata();
        let ok = true;
        for (const update of futureRoles) {
            const currentRole = currentRoles.find(r => r.Name.trim().toLowerCase() === update.Name.trim().toLowerCase());
            if (currentRole) {
                currentRole.Description = update.Description;
                ok = ok && await currentRole.Save();
            }
        }
        return { Success: ok };
    }
    async AddNewRoles(currentRoles, futureRoles, user, userPayload) {
        // go through the future roles and add any that are not in the current roles
        const md = new Metadata();
        let ok = true;
        for (const add of futureRoles) {
            if (!currentRoles.find(r => r.Name.trim().toLowerCase() === add.Name.trim().toLowerCase())) {
                const role = await md.GetEntityObject("MJ: Roles", user);
                role.Name = add.Name;
                role.Description = add.Description;
                ok = ok && await role.Save();
            }
        }
        return ok;
    }
    async DeleteRemovedRoles(currentRoles, futureRoles, user, userPayload) {
        const rv = new RunView();
        let ok = true;
        // iterate through the existing roles and remove any that are not in the input
        for (const remove of currentRoles) {
            if (!this.IsStandardRole(remove.Name)) {
                if (!futureRoles.find(r => r.Name.trim().toLowerCase() === remove.Name.trim().toLowerCase())) {
                    ok = ok && await this.DeleteSingleRole(remove, rv, user, userPayload);
                }
            }
        }
        return ok;
    }
    get StandardRoles() {
        return ['Developer', 'Integration', 'UI'];
    }
    IsStandardRole(roleName) {
        return this.StandardRoles.find(r => r.toLowerCase() === roleName.toLowerCase()) !== undefined;
    }
    async DeleteSingleRole(role, rv, user, userPayload) {
        // first, remove all the UserRole records that match this role
        let ok = true;
        const r2 = await rv.RunView({
            EntityName: "MJ: User Roles",
            ExtraFilter: "RoleID = '" + role.ID + "'",
            ResultType: 'entity_object'
        }, user);
        if (r2.Success) {
            for (const ur of r2.Results) {
                ok = ok && await ur.Delete(); // remove the user role
            }
        }
        return ok && role.Delete(); // remove the role
    }
    /**
     * This mutation will sync the just the users in the system with the data provided in the input, matches existing users by email
     * @important This method will NOT work if the roles are not already in sync, meaning if User/Role relationships exist in the input data where the Role doesn't already exist in this system the sync will fail
     * @param data
     */
    async SyncUsers(users, context) {
        try {
            // first, we sync up the users and then the user roles. 
            // for syncing users we first remove users that are no longer in the input, then we add new users and update existing users
            const rv = new RunView();
            const result = await rv.RunView({
                EntityName: "MJ: Users",
                ResultType: 'entity_object'
            }, context.userPayload.userRecord);
            if (result && result.Success) {
                // go through current users and remove those that are not in the input
                const currentUsers = result.Results;
                if (await this.DeleteRemovedUsers(currentUsers, users, context.userPayload.userRecord, context.userPayload)) {
                    if (await this.AddNewUsers(currentUsers, users, context.userPayload)) {
                        if (await this.UpdateExistingUsers(currentUsers, users, context.userPayload)) {
                            if (await this.SyncUserRoles(users, context.userPayload.userRecord, context.userPayload)) {
                                return { Success: true };
                            }
                        }
                    }
                }
            }
            return { Success: false }; // if we get here, something went wrong
        }
        catch (err) {
            LogError(err);
            throw new Error('Error syncing roles and users\n\n' + err);
        }
    }
    async UpdateExistingUsers(currentUsers, futureUsers, userPayload) {
        // go through the future users and update any that are in the current users
        let ok = true;
        for (const update of futureUsers) {
            const current = currentUsers.find(c => c.Email?.trim().toLowerCase() === update.Email?.trim().toLowerCase());
            if (current) {
                current.Name = update.Name;
                current.Type = update.Type;
                current.FirstName = update.FirstName;
                current.LastName = update.LastName;
                current.Title = update.Title;
                ok = ok && await current.Save();
            }
        }
        return ok;
    }
    async AddNewUsers(currentUsers, futureUsers, userPayload) {
        // add users that are not in the current users
        const md = new Metadata();
        let ok = true;
        for (const add of futureUsers) {
            const match = currentUsers.find(currentUser => currentUser.Email?.trim().toLowerCase() === add.Email?.trim().toLowerCase());
            if (match) {
                // make sure the IsActive bit is set to true
                match.IsActive = true;
                ok = ok && await match.Save();
            }
            else {
                const user = await md.GetEntityObject("MJ: Users", userPayload.userRecord);
                user.Name = add.Name;
                user.Type = add.Type;
                user.Email = add.Email;
                user.FirstName = add.FirstName;
                user.LastName = add.LastName;
                user.Title = add.Title;
                user.IsActive = true;
                ok = ok && await user.Save();
            }
        }
        return ok;
    }
    async DeleteRemovedUsers(currentUsers, futureUsers, u, userPayload) {
        // remove users that are not in the future users
        const rv = new RunView();
        const md = new Metadata();
        let ok = true;
        //const tg = await md.CreateTransactionGroup(); HAVING PROBLEMS with this, so skipping for now, I think the entire thing is wrapped in a transaction and that's causing issues with two styles of trans wrappers
        for (const remove of currentUsers) {
            if (remove.Type.trim().toLowerCase() !== 'owner') {
                if (!futureUsers.find(r => r.Email.trim().toLowerCase() === remove.Email.trim().toLowerCase())) {
                    ok = ok && await this.DeleteSingleUser(remove, rv, u, userPayload);
                }
            }
        }
        return ok;
    }
    async DeleteSingleUser(user, rv, u, userPayload) {
        // first, remove all the UserRole records that match this user
        let ok = true;
        const r2 = await rv.RunView({
            EntityName: "MJ: User Roles",
            ExtraFilter: "UserID = '" + user.ID + "'",
            ResultType: 'entity_object'
        }, u);
        if (r2.Success) {
            for (const ur of r2.Results) {
                //ur.TransactionGroup = tg;
                ok = ok && await ur.Delete(); // remove the user role
            }
        }
        if (await user.Delete()) {
            return ok;
        }
        else {
            // in some cases there are a lot of fkey constraints that prevent the user from being deleted, so we mark the user as inactive instead
            user.IsActive = false;
            return await user.Save() && ok;
        }
    }
    async SyncUserRoles(users, u, userPayload) {
        // for each user in the users array, make sure there is a User Role that matches. First, get a list of all DATABASE user and roels so we have that for fast lookup in memory
        const rv = new RunView();
        const md = new Metadata();
        const p1 = rv.RunView({
            EntityName: "MJ: Users",
            ResultType: 'entity_object'
        }, u);
        const p2 = rv.RunView({
            EntityName: "MJ: Roles",
            ResultType: 'entity_object'
        }, u);
        const p3 = rv.RunView({
            EntityName: "MJ: User Roles",
            ResultType: 'entity_object'
        }, u);
        // await both 
        const [uResult, rResult, urResult] = await Promise.all([p1, p2, p3]);
        if (uResult.Success && rResult.Success && urResult.Success) {
            // we have the DB users and roles, and user roles
            const dbUsers = uResult.Results;
            const dbRoles = rResult.Results;
            const dbUserRoles = urResult.Results;
            let ok = true;
            // now, we can do lookups in memory from those DB roles and Users for their ID values
            // now we will iterate through the users input type and for each role, make sure it is in there     
            //const tg = await md.CreateTransactionGroup();
            for (const user of users) {
                const dbUser = dbUsers.find(u => u.Email.trim().toLowerCase() === user.Email.trim().toLowerCase());
                if (dbUser) {
                    for (const role of user.Roles) {
                        const dbRole = dbRoles.find(r => r.Name.trim().toLowerCase() === role.Name.trim().toLowerCase());
                        if (dbRole) {
                            // now we need to make sure there is a user role that matches this user and role
                            if (!dbUserRoles.find(ur => UUIDsEqual(ur.UserID, dbUser.ID) && UUIDsEqual(ur.RoleID, dbRole.ID))) {
                                // we need to add a user role
                                const ur = await md.GetEntityObject("MJ: User Roles", u);
                                ur.UserID = dbUser.ID;
                                ur.RoleID = dbRole.ID;
                                ok = ok && await ur.Save();
                            }
                        }
                    }
                    // now, we check for DB user roles that are NOT in the user.Roles property as they are no longer part of the user's roles
                    const thisUserDBRoles = dbUserRoles.filter(ur => UUIDsEqual(ur.UserID, dbUser.ID));
                    for (const dbUserRole of thisUserDBRoles) {
                        const role = user.Roles.find(r => r.Name.trim().toLowerCase() === dbRoles.find(rr => UUIDsEqual(rr.ID, dbUserRole.RoleID))?.Name.trim().toLowerCase());
                        if (!role && !this.IsStandardRole(dbUserRole.Role)) {
                            // this user role is no longer in the user's roles, we need to remove it
                            //dbUserRole.TransactionGroup = tg;
                            ok = ok && await dbUserRole.Delete(); // remove the user role - we use await for the DELETE, not the save
                        }
                    }
                }
            }
            return ok;
        }
        else {
            return false;
        }
    }
}
__decorate([
    RequireSystemUser(),
    Mutation(() => SyncRolesAndUsersResultType),
    __param(0, Arg('data', () => RolesAndUsersInputType)),
    __param(1, Ctx()),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [RolesAndUsersInputType, Object]),
    __metadata("design:returntype", Promise)
], SyncRolesAndUsersResolver.prototype, "SyncRolesAndUsers", null);
__decorate([
    RequireSystemUser(),
    Mutation(() => SyncRolesAndUsersResultType),
    __param(0, Arg('roles', () => [RoleInputType])),
    __param(1, Ctx()),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Array, Object]),
    __metadata("design:returntype", Promise)
], SyncRolesAndUsersResolver.prototype, "SyncRoles", null);
__decorate([
    RequireSystemUser(),
    Mutation(() => SyncRolesAndUsersResultType),
    __param(0, Arg('users', () => [UserInputType])),
    __param(1, Ctx()),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Array, Object]),
    __metadata("design:returntype", Promise)
], SyncRolesAndUsersResolver.prototype, "SyncUsers", null);
//# sourceMappingURL=SyncRolesUsersResolver.js.map