package dmo.fs.db.openapi;

import dmo.fs.db.MessageUser;
import dmo.fs.db.wsnext.DbConfiguration;
import dmo.fs.db.wsnext.hib.DodexHibernateConfig;
import dmo.fs.db.wsnext.hib.srv.DodexService;
import dmo.fs.entities.Group;
import dmo.fs.entities.Group_;
import dmo.fs.entities.Member;
import dmo.fs.entities.Users;
import dmo.fs.utils.DodexUtil;
import dmo.fs.utils.Err;
import io.smallrye.mutiny.Uni;
import io.vertx.core.Context;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.mutiny.core.Promise;
import jakarta.persistence.NoResultException;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
import org.hibernate.reactive.mutiny.Mutiny;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.Serial;
import java.io.Serializable;
import java.sql.SQLException;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;

public class GroupHibernateService extends DodexService implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    protected final static Logger logger = LoggerFactory.getLogger(GroupHibernateService.class.getName());
    private final static DodexHibernateConfig dodexHibernateConfig;
    protected final static Mutiny.SessionFactory sessionFactory;

    static {
        try {
            dodexHibernateConfig = DbConfiguration.getDefaultDb();
            dodexHibernateConfig.configDatabase();
            sessionFactory = dodexHibernateConfig.getSessionFactory();
        } catch (InterruptedException | IOException | SQLException e) {
            throw new RuntimeException(e);
        }
    }

    static Boolean isCheckForOwner = true;

    public GroupHibernateService(Boolean isCheckForOwner) {
        GroupHibernateService.isCheckForOwner = isCheckForOwner;
    }

    public GroupHibernateService() {
    }

    private final static DodexUtil dodexUtil = new DodexUtil();

    protected static boolean qmark = true;
    protected final static DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.systemDefault());
    ZoneId zoneId = ZoneId.of("Europe/London");


    protected Uni<Group> getGroupByName(String name)
      throws NoResultException {
        Promise<Group> groupPromise = Promise.promise();
        Context duplicatedContext = getVertxContext();

        duplicatedContext.runOnContext(v -> {
            sessionFactory.openSession().onItemOrFailure().invoke((session, err) -> {
                if (err != null) {
                    throw new RuntimeException(err);
                }
                CriteriaBuilder builder = session.getCriteriaBuilder();
                CriteriaQuery<Group> groupQuery = builder.createQuery(Group.class);
                Root<Group> root = groupQuery.from(Group.class);
                groupQuery.select(root).where(builder.equal(root.get(Group_.NAME), name));
                session.createQuery(groupQuery).getSingleResult()
                  .onItemOrFailure().invoke((group, err2) -> {
                      if (err2 != null) {
                          logger.error(Err.displayErr(56), err2.getMessage());
                      }
                      if (group == null) {
                          group = new Group();
                          group.setId(0L);
                      }
                      List<Group> groupArray = new ArrayList<>();
                      groupArray.add(group);
                      session.close().onItemOrFailure().invoke((v2, err3) -> {
                          groupPromise.complete(groupArray.getFirst());
                      }).subscribe().asCompletionStage();
                  }).subscribe().asCompletionStage();
            }).subscribe().asCompletionStage();
        });
        return groupPromise.future();
    }

    protected Uni<Users> getUserById(Long userId) {
        Promise<Users> usersPromise = Promise.promise();
        Context duplicatedContext = getVertxContext();

        duplicatedContext.runOnContext(v -> {
            sessionFactory.openSession().onItemOrFailure().invoke((session, err) -> {
                if (err != null) {
                    session.close().subscribe().asCompletionStage();
                    logger.error("{}{}", Err.displayErr(39), err.getMessage());
                    throw new RuntimeException(err);
                }
                CriteriaBuilder builder = session.getCriteriaBuilder();
                CriteriaQuery<Users> userQuery = builder.createQuery(Users.class);
                Root<Users> root = userQuery.from(Users.class);
                userQuery.select(root).where(builder.equal(root.get("id"), userId));

                Mutiny.SelectionQuery<Users> query = session.createQuery(userQuery);
                query.getSingleResult().onItemOrFailure().invoke((users, err2) -> {
                    if (err2 != null) {
                        session.close().subscribe().asCompletionStage();
                        logger.error(Err.displayErr(61), err2.getMessage());
                        throw new RuntimeException(err2);
                    }
                    usersPromise.complete(users);
                    session.close().subscribe().asCompletionStage();
                }).subscribe().asCompletionStage();
            }).subscribe().asCompletionStage();
        });

        return usersPromise.future();
    }

    protected Uni<Users> getUserByName(String name) {
        Promise<Users> usersPromise = Promise.promise();
        Context duplicatedContext = getVertxContext();

        duplicatedContext.runOnContext(v -> {
            sessionFactory.openSession().onItemOrFailure().invoke((session, err) -> {
                if (err != null) {
                    session.close().subscribe().asCompletionStage();
                    logger.error("{}{}", Err.displayErr(39), err.getMessage());
                    throw new RuntimeException(err);
                }
                CriteriaBuilder builder = session.getCriteriaBuilder();
                CriteriaQuery<Users> userQuery = builder.createQuery(Users.class);
                Root<Users> root = userQuery.from(Users.class);
                userQuery.select(root).where(builder.equal(root.get("name"), name));

                Mutiny.SelectionQuery<Users> query = session.createQuery(userQuery);
                query.getSingleResult().onItemOrFailure().invoke((users, err2) -> {
                    if (err2 != null) {
                        session.close().subscribe().asCompletionStage();
                        logger.error(Err.displayErr(57), err2.getMessage());
                        throw new RuntimeException(err2);
                    }
                    usersPromise.complete(users);
                    session.close().subscribe().asCompletionStage();
                }).subscribe().asCompletionStage();
            }).subscribe().asCompletionStage();
        });

        return usersPromise.future();
    }

    protected Uni<List<Users>> getMembersByGroup(Group group) {
        Promise<List<Users>> usersPromise = Promise.promise();
        Context duplicatedContext = getVertxContext();

        duplicatedContext.runOnContext(v -> {
            sessionFactory.openSession().onItemOrFailure().invoke((session, err) -> {
                CriteriaBuilder builder = session.getCriteriaBuilder();
                CriteriaQuery<Users> criteriaQuery = builder.createQuery(Users.class);
                CriteriaQuery<Group> rootQuery = builder.createQuery(Group.class);

                Root<Group> groupRoot = criteriaQuery.from(Group.class);
                Join<Group, Users> members = groupRoot.join("users");
                criteriaQuery.where(builder.equal(groupRoot.get("id"), group.getId()));

                Mutiny.SelectionQuery<Users> query = session.createQuery(criteriaQuery.select(members));
                query.getResultList().onItemOrFailure().invoke((usersList, err2) -> {
                    if (err2 != null) {
                        usersPromise.complete(usersList);
                        session.close().subscribe().with(v2 -> {
                            throw new RuntimeException(err2);
                        });
                    }
                    usersPromise.complete(usersList);
                    session.close().subscribe().asCompletionStage();
                }).subscribe().asCompletionStage();

            }).subscribe().asCompletionStage();
        });

        return usersPromise.future();
    }

    public Uni<JsonObject> addGroupAndMembers(final JsonObject addGroupJson) {
        Promise<JsonObject> promiseJsonObject = Promise.promise();
        Context duplicatedContext = getVertxContext();

        MessageUser messageUser = dodexHibernateConfig.createMessageUser();
        messageUser.setName(addGroupJson.getString("groupOwner"));
        messageUser.setPassword(addGroupJson.getString("ownerId"));
        try {
            Uni<MessageUser> messageUserSelected = selectUser(messageUser);

            duplicatedContext.runOnContext(v -> {
                if (sessionFactory == null) {
                    promiseJsonObject.complete(addGroupJson);
                    return;
                }

                final Map<String, String> selected = DodexUtil.commandMessage(addGroupJson.getString("groupMessage"));
                final List<String> selectedUsers = Arrays.asList(selected.get("selectedUsers").split(","));

                sessionFactory.openSession().onItemOrFailure().invoke((session, err) -> {
                    if (err != null) {
                        logger.error(Err.displayErr(39), err.getMessage());
                        session.close().subscribe().asCompletionStage();
                        promiseJsonObject.complete(addGroupJson);
                        throw new RuntimeException(err.getMessage());
                    }
                    messageUserSelected.onItemOrFailure().invoke((selectedMessageUser, err2) -> {
                        if (err2 != null) {
                            logger.error(Err.displayErr(53), err2.getMessage());
                            session.close().subscribe().asCompletionStage();
                            promiseJsonObject.complete(addGroupJson);
                            throw new RuntimeException(err2.getMessage());
                        }

                        Uni<JsonObject> addGroup;
                        try {
                            addGroup = addGroup(addGroupJson, selectedMessageUser);
                        } catch (IOException e) {
                            session.close().subscribe().asCompletionStage();
                            promiseJsonObject.complete(addGroupJson);
                            throw new RuntimeException(e);
                        }

                        addGroup.onItemOrFailure().invoke((selectedUsersJson, err3) -> {
                            if (err3 != null) {
                                logger.error(Err.displayErr(59), err3.getMessage());
                                session.close().subscribe().asCompletionStage();
                                promiseJsonObject.complete(addGroupJson);
                                throw new RuntimeException(err3);
                            }

                            String entry0 = selectedUsers.getFirst();
                            if (addGroupJson.getInteger("status") == 0 &&
                              entry0 != null && !entry0.isEmpty()) {
                                try {
                                    Uni<JsonObject> groupJson = addMembers(session, selectedUsers, addGroupJson);
                                    groupJson.onItemOrFailure().invoke((json, err4) -> {
                                        if (err4 != null) {
                                            session.close().subscribe().asCompletionStage();
                                            promiseJsonObject.complete(json);
                                            throw new RuntimeException(err4);
                                        }
                                        promiseJsonObject.complete(json);
                                    }).subscribe().asCompletionStage();
                                } catch (SQLException | InterruptedException | IOException err5) {
                                    addGroupJson
                                      .put("status", -1)
                                      .put("errorMessage", err5.getMessage());
                                    promiseJsonObject.complete(addGroupJson);
                                    throw new RuntimeException(err5);
                                } catch (ExecutionException e) {
                                    throw new RuntimeException(e);
                                }
                            } else {
                                if (selectedUsers.getFirst().isEmpty()) {
                                    addGroupJson.put("errorMessage", "No handles selected?")
                                      .put("status", -1);
                                }
                                promiseJsonObject.complete(addGroupJson);
                            }
                        }).subscribe().asCompletionStage();
                    }).subscribe().asCompletionStage();
                }).subscribe().asCompletionStage();
            });
        } catch (IOException e) {
            promiseJsonObject.complete(addGroupJson);
            throw new RuntimeException(e);
        }
        return promiseJsonObject.future();
    }

    protected Uni<JsonObject> addGroup(JsonObject addGroupJson, MessageUser messageUserSelected)
      throws IOException {
        LocalDateTime ldt = LocalDateTime.now();
        Promise<JsonObject> promiseAddGroup = Promise.promise();

        Uni<Group> uniGroup = getGroupByName(addGroupJson.getString("groupName"));
        uniGroup.onItemOrFailure().invoke((group, err) -> {
            if (err != null) {
                logger.error(Err.displayErr(56), err.getMessage());
            }

            if (group != null && group.getId() != 0) {
                addGroupJson
                  .put("ownerKey", messageUserSelected.getId())
                  .put("id", group.getId())
                  .put("status", 0)
                  .put("errorMessage", "");
                promiseAddGroup.complete(addGroupJson);
            } else {
                Group newGroup = new Group();
                newGroup.setName(addGroupJson.getString("groupName"));
                newGroup.setCreated(ldt);
                newGroup.setUpdated(ldt);
                newGroup.setOwner(messageUserSelected.getId().intValue());
                Context duplicatedContext = getVertxContext();
                duplicatedContext.runOnContext(v -> {
                    sessionFactory.openSession().onItemOrFailure().invoke((session, err2) -> {
                        if (err2 != null) {
                            logger.error("{}{}", Err.displayErr(39), err2.getMessage());
                        }

                        session.persist(newGroup).onItemOrFailure().invoke((v2, err3) -> {
                            if (err3 != null) {
                                session.close().subscribe().asCompletionStage();
                                promiseAddGroup.complete(addGroupJson);
                                throw new RuntimeException(err3);
                            }
                            session.close().subscribe().asCompletionStage();

                            ZoneOffset preferredOffset = zoneId.getRules().getOffset(ldt);
                            ZonedDateTime zonedDateTime = ZonedDateTime.ofLocal(ldt, zoneId, preferredOffset);

                            String openApiDate = zonedDateTime.format(formatter);

                            addGroupJson
                              .put("ownerKey", messageUserSelected.getId())
                              .put("id", newGroup.getId())
                              .put("status", 0)
                              .put("created", openApiDate)
                              .put("groupMessage", "Group added.")
                              .put("errorMessage", "Group added.");
                            promiseAddGroup.complete(addGroupJson);
                        }).subscribe().asCompletionStage();
                    }).subscribe().asCompletionStage();
                });
            }
        }).subscribe().asCompletionStage();

        return promiseAddGroup.future();
    }

    protected Uni<JsonObject> addMembers(Mutiny.Session session2, List<String> selectedUsers, final JsonObject addGroupJson)
      throws InterruptedException, SQLException, IOException, ExecutionException {
        Promise<JsonObject> memberPromise = Promise.promise();
        Context duplicatedContext = getVertxContext();

        duplicatedContext.runOnContext(v -> {
            sessionFactory.openSession().onItemOrFailure().invoke((session, err) -> {
                if (err != null) {
                    throw new RuntimeException(err);
                }

                session.find(Group.class, addGroupJson.getInteger("id")).onItemOrFailure().invoke((group, err2) -> {
                    if (err2 != null) {
                        logger.error(Err.displayErr(54), err2.getMessage());
                        throw new RuntimeException(err2);
                    }
                    checkOnGroupOwner(addGroupJson).onItemOrFailure().invoke((checkedJson, err3) -> {
                        if (checkedJson.getBoolean("isValidForOperation")) {
                            session.flush().onItemOrFailure().invoke((v3, err7) -> {
                                session.clear();
                            }).subscribe().asCompletionStage();
                            session.close().onItemOrFailure().invoke((s, err6) -> {
                            }).subscribe().asCompletionStage();

                            List<String> allUsers = new ArrayList<>();
                            allUsers.add(addGroupJson.getString("groupOwner"));
                            allUsers.addAll(selectedUsers);
                            checkOnMembers(group, allUsers).onItemOrFailure().invoke((newMembers, err4) -> {
                                if(err4 != null) {
                                    logger.error(Err.displayErr(65), err4.getMessage());
                                }
                                List<Long> userIds = new ArrayList<>();
                                Iterator<String> memberIter = newMembers.iterator();
                                AtomicInteger counter = new AtomicInteger();

                                memberIter.forEachRemaining(memberName -> {
                                    getUserByName(memberName).onItemOrFailure().invoke((user, err5) -> {
                                        if (err5 != null) {
                                            logger.error("{}{}", Err.displayErr(4), err5.getMessage());
                                            throw new RuntimeException(err5);
                                        }
//    logger.info("UserIds addin Members***********: {}", user.getId());
                                        userIds.add(user.getId());
                                        if (counter.incrementAndGet() == newMembers.size()) {
                                            if (!userIds.isEmpty()) {
                                                List<Member> members = userIds.stream()
                                                  .map(userId -> {
                                                      Member member = new Member();
                                                      member.setMemberId(new Member.MemberId(group.getId(), userId));
                                                      return member;
                                                  }).toList();
//logger.info("Before Persist - Members to add: {}", members.size());
                                                persistMembers(members);
//logger.info("After persistMember**************");
                                                addGroupJson.put("errorMessage", "Member(s) added");
                                                memberPromise.complete(addGroupJson);
                                            }
                                        }
                                    }).subscribe().asCompletionStage();
                                });
                            }).subscribe().asCompletionStage();
                        } else {
                            session.close().subscribe().asCompletionStage();
                            memberPromise.complete(addGroupJson);
                        }
                    }).subscribe().asCompletionStage();
                }).subscribe().asCompletionStage();
            }).subscribe().asCompletionStage();
        });

        return memberPromise.future();
    }

    protected void persistMembers(List<Member> memberList) {
        Context duplicatedContext = getVertxContext();
        duplicatedContext.runOnContext(v -> {
            sessionFactory.openSession().onItemOrFailure().invoke((session, err) -> {
                if (err != null) {
                    throw new RuntimeException(err);
                }
                memberList.iterator().forEachRemaining(member -> {
                    session.persist(member).onItemOrFailure().invoke((v2, err6) -> {
                        if (err6 != null) {
                            logger.error(Err.displayErr(58), err6.getMessage());
                            throw new RuntimeException(err6);
                        }
                    }).subscribe().asCompletionStage();
                });
                session.flush().onItemOrFailure().invoke((v3, err2) -> {
                    session.clear();
                    session.close().subscribe().asCompletionStage();
                }).subscribe().asCompletionStage();
            }).subscribe().asCompletionStage();
        });
    }

    public Uni<JsonObject> deleteGroupOrMembers(final JsonObject deleteGroupJson)
      throws NoResultException {
        Promise<JsonObject> deletePromise = Promise.promise();
        MessageUser messageUser = dodexHibernateConfig.createMessageUser();
        Map<String, String> selected = DodexUtil.commandMessage(deleteGroupJson.getString("groupMessage"));
        final List<String> selectedUsers = Arrays.asList(selected.get("selectedUsers").split(","));

        messageUser.setName(deleteGroupJson.getString("groupOwner"));
        messageUser.setPassword(deleteGroupJson.getString("ownerId"));

        String entry0 = selectedUsers.getFirst();
        if (deleteGroupJson.getInteger("status") == 0 && "" .equals(entry0)) {
            try {
                deleteGroup(deleteGroupJson).onItemOrFailure().invoke((groupJson, err) -> {
                    if (err != null) {
                        throw new RuntimeException(err);
                    }
                    deletePromise.complete(groupJson);
                }).subscribe().asCompletionStage();

            } catch (Exception err2) {
                errData(err2, deleteGroupJson);
            }
        } else if (deleteGroupJson.getInteger("status") == 0) {
            try {
                deleteMembers(selectedUsers, deleteGroupJson)
                  .onItemOrFailure().invoke((deleteJson, err1) -> {
                      if (err1 != null) {
                          logger.error(Err.displayErr(62), err1.getMessage());
                          throw new RuntimeException(err1);
                      }
                      deletePromise.complete(deleteJson);
                  }).subscribe().asCompletionStage();

            } catch (Exception err3) {
                errData(err3, deleteGroupJson);
            }
        }

        return deletePromise.future();
    }

    public Uni<JsonObject> deleteGroup(JsonObject deleteGroupJson) {
        Context duplicatedContext = getVertxContext();
        Promise<JsonObject> deleteJson = Promise.promise();

        checkOnGroupOwner(deleteGroupJson).onItemOrFailure()
          .invoke((checkedJson, err2) -> {
              if (deleteGroupJson.getString("errorMessage") != null &&
                !deleteGroupJson.getString("errorMessage").startsWith("No result")) {
                  if (checkedJson.getBoolean("isValidForOperation")) {
                      Uni<Group> uniGroup = getGroupByName(deleteGroupJson.getString("groupName"));
                      uniGroup.onItemOrFailure().invoke((group, err3) -> {
                          duplicatedContext.runOnContext(v -> {
                              sessionFactory.openSession().onItemOrFailure().invoke((session, err) -> {
                                  if (err3 != null) {
                                      session.close().subscribe().asCompletionStage();
                                      deleteJson.complete(deleteGroupJson);
                                      throw new RuntimeException(err3);
                                  }
                                  int deletedMembers = group.getMembers().size();
                                  session.merge(group).onItemOrFailure().invoke((mergedGroup, err1) -> {
                                      if (err1 != null) {
                                          logger.error(Err.displayErr(64), err.getMessage());
                                          session.close().subscribe().asCompletionStage();
                                          deleteJson.complete(deleteGroupJson);
                                          throw new RuntimeException(err1);
                                      }

                                      session.remove(mergedGroup).onItemOrFailure().invoke((v2, err4) -> {
                                          if (err4 != null) {
                                              logger.error(Err.displayErr(60), err4.getMessage());
                                              session.close().subscribe().asCompletionStage();
                                              deleteJson.complete(deleteGroupJson);
                                              throw new RuntimeException(err4);
                                          }
                                          String deleteMessage = null;
                                          if (deletedMembers > 0) {
                                              deleteMessage = deletedMembers + " members with group deleted";
                                          } else {
                                              deleteMessage = "Group " + group.getName() + " deleted.";
                                          }

                                          deleteGroupJson.put("errorMessage", deleteMessage);
                                          session.flush().invoke(v3 -> {
                                              session.clear();
                                              session.close().subscribe().asCompletionStage();
                                              deleteGroupJson.put("errorMessage", "Group Deleted");
                                              deleteJson.complete(deleteGroupJson);
                                          }).subscribe().asCompletionStage();
                                      }).subscribe().asCompletionStage();
                                  }).subscribe().asCompletionStage();
                              }).subscribe().asCompletionStage();
                          });
                      }).subscribe().asCompletionStage();
                  } else {
                      deleteGroupJson
                        .put("isValidForOperation", checkedJson.getBoolean("isValidForOperation"))
                        .put("errorMessage", "Contact owner for group administration");
                      deleteJson.complete(deleteGroupJson);
                  }
              } else {
                  deleteGroupJson
                  .put("isValidForOperation", false);
                  if(deleteGroupJson.getString("errorMessage") == null) {
                      deleteGroupJson.put("errorMessage", "No Deletion occurred");
                  }
                  deleteJson.complete(deleteGroupJson);
              }
          }).subscribe().asCompletionStage();

        return deleteJson.future();
    }

    protected Uni<JsonObject> deleteMembers
      (List<String> selectedUsers, JsonObject deleteGroupJson) {
        Promise<JsonObject> membersPromise = Promise.promise();
        Context duplicatedContext = getVertxContext();

        checkOnGroupOwner(deleteGroupJson).onItemOrFailure()
          .invoke((checkedJson, err1) -> {
              if (checkedJson.getBoolean("isValidForOperation")) {
                  getGroupByName(deleteGroupJson.getString("groupName"))
                    .onItemOrFailure().invoke((group, err2) -> {
                        deleteGroupJson.put("id", group.getId());
                        for (Member member : group.getMembers()) {
                            getUserById(member.getMemberId().getUser_id()).onItemOrFailure()
                              .invoke((user, err3) -> {
                                  if (err3 != null) {
                                      logger.error(Err.displayErr(61), err3.getMessage());
                                      throw new RuntimeException(err3);
                                  }
                                  if (selectedUsers.contains(user.getName())) {
                                      duplicatedContext.runOnContext(v -> {
                                          sessionFactory.openSession().onItemOrFailure()
                                            .invoke((session, err) -> {
                                                if (err != null) {
                                                    logger.error(Err.displayErr(69), err.getMessage());
                                                    throw new RuntimeException(err);
                                                }
                                                if (user.getId().equals(member.getMemberId().getUser_id()) &&
                                                  group.getId().equals(member.getMemberId().getGroup_id())) {
                                                    session.find(Member.class,
                                                        new Member.MemberId(group.getId(), user.getId()))
                                                      .onItemOrFailure().invoke((removeMember, err4) -> {
                                                          if (err4 != null) {
                                                              logger.error(Err.displayErr(63), err4.getMessage());
                                                              session.close().subscribe().asCompletionStage();
                                                              throw new RuntimeException(err4);
                                                          }
                                                          session.remove(removeMember).onItemOrFailure()
                                                            .invoke((v2, err5) -> {
                                                                if (err5 != null) {
                                                                    logger.error(Err.displayErr(62), err5.getMessage());
                                                                    session.close().subscribe().asCompletionStage();
                                                                    throw new RuntimeException(err5);
                                                                }
                                                                session.flush().invoke(v3 -> {
                                                                    session.clear();
                                                                    session.close().subscribe().asCompletionStage();
                                                                }).subscribe().asCompletionStage();
                                                            }).subscribe().asCompletionStage();
                                                      }).subscribe().asCompletionStage();
                                                }
                                            }).subscribe().asCompletionStage();
                                      });
                                  }
                              }).subscribe().asCompletionStage();
                        }

                        deleteGroupJson
                          .put("groupMessage", "")
                          .put("errorMessage", "Group member(s) removed");
                        membersPromise.complete(deleteGroupJson);
                    }).subscribe().asCompletionStage();

              } else {
                  deleteGroupJson
                    .put("isValidForOperation", checkedJson.getBoolean("isValidForOperation"))
                    .put("errorMessage", "Contact owner for group administration");
                  membersPromise.complete(deleteGroupJson);
              }
          }).subscribe().asCompletionStage();

        return membersPromise.future();
    }

    public Uni<JsonObject> getMembersList(JsonObject getGroupJson) {
        Promise<JsonObject> jsonPromise = Promise.promise();
        Context duplicatedContext = getVertxContext();

        if (sessionFactory == null) {
            jsonPromise.complete(getGroupJson);
            throw new RuntimeException("null SessionFactory");
        }

        duplicatedContext.runOnContext(v -> {
            MessageUser messageUser = dodexHibernateConfig.createMessageUser();

            messageUser.setName(getGroupJson.getString("groupOwner"));
            messageUser.setPassword(getGroupJson.getString("ownerId"));
            sessionFactory.openSession().onItemOrFailure().invoke((session, err) -> {
                if (err != null) {
                    logger.error(Err.displayErr(39), err.getMessage());
                    session.close().subscribe().asCompletionStage();
                    throw new RuntimeException(err.getMessage());
                }
                Uni<MessageUser> selected;
                try {
                    selected = selectUser(messageUser).onItemOrFailure().invoke((mUser, err1) -> {
                        if (err1 != null) {
                            session.close().subscribe().asCompletionStage();
                            throw new RuntimeException(err1);
                        }
                    });
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                selected.onItemOrFailure().invoke((mUser, err1) -> {
                    if (err1 != null) {
                        session.close().subscribe().asCompletionStage();
                        throw new RuntimeException(err1);
                    }
                    getGroupJson.put("ownerKey", mUser.getId());

                    getGroupByName(getGroupJson.getString("groupName"))
                      .onItemOrFailure().invoke((group, err2) -> {
                          if (err2 != null) {
                              throw new NoResultException(err2.getMessage());
                          }

                          if (group == null) {
                              getGroupJson
                                .put("errorMessage", "Group not found: " + getGroupJson.getString("groupName"))
                                .put("id", 0);
                              jsonPromise.complete(getGroupJson);
                              throw new NoResultException("Group Not Found");
                          }
                          getMembersByGroup(group)
                            .onItemOrFailure().invoke((users, err3) -> {
                                if (err3 != null) {
                                    logger.error(Err.displayErr(55), err3.getMessage());
                                    session.close().subscribe().with(v2 -> {
                                        throw new NoResultException(err3.getMessage());
                                    });
                                }

                                JsonArray members = new JsonArray();
                                if (!users.isEmpty()) {
                                    for (Users user : users) {
                                        if (!user.getName().equals(getGroupJson.getString("groupOwner"))) {
                                            members.add(new JsonObject().put("name", user.getName()));
                                        }
                                    }
                                    getGroupJson.put("groupMessage", "")
                                      .put("members", members.encode())
                                      .put("errorMessage", "")
                                      .put("id", group.getId());
                                    jsonPromise.complete(getGroupJson);
                                } else {
                                    if (group != null && group.getId() > 0) {
                                        getGroupJson.put("id", group.getId())
                                          .put("errorMessage", "Group found: " + getGroupJson.getString("groupName"));
                                    } else {
                                        getGroupJson
                                          .put("errorMessage", "Group not found: " + getGroupJson.getString("groupName"))
                                          .put("id", 0);
                                    }
                                    jsonPromise.complete(getGroupJson);
                                }
                            }).subscribe().asCompletionStage();
                      }).subscribe().asCompletionStage();
                }).subscribe().asCompletionStage();
            }).subscribe().asCompletionStage();
        });
        return jsonPromise.future();
    }

    protected Uni<JsonObject> checkOnGroupOwner(JsonObject groupJson) {
        Promise<JsonObject> ownerPromise = Promise.promise();

        getGroupByName(groupJson.getString("groupName")).onItemOrFailure().invoke((group, err) -> {
            if (err != null) {
                logger.error(Err.displayErr(56), err.getMessage());
                groupJson.put("errorMessage", err.getMessage());
                groupJson.put("id", 0);
                ownerPromise.complete(groupJson);
            } else {
                getUserByName(groupJson.getString("groupOwner")).onItemOrFailure()
                  .invoke((user, err2) -> {
                      if (err2 != null) {
                          logger.error("{}{}", Err.displayErr(4), err2.getMessage());
                          groupJson.put("errorMessage", err2.getMessage());
                          groupJson.put("id", 0);
                          ownerPromise.complete(groupJson);
                          return;
                      }
                      groupJson.put("isValidForOperation", false);

                      if (group.getId() != 0) {
                          groupJson.put("id", group.getId())
                            .put("ownerKey", group.getOwner());
                      }

                      JsonObject checkGroupJson = new JsonObject()
                        .put("checkGroupOwnerId", user.getId())
                        .put("checkGroupOwner", user.getName())
                        .put("checkForOwner", isCheckForOwner != null && isCheckForOwner)
                        .put("isValidForOperation", groupJson.getInteger("status") != -1 &&
                          !isCheckForOwner ||
                          Objects.equals(user.getId(), Long.valueOf(groupJson.getInteger("ownerKey").toString())));

                      groupJson
                        .put("isValidForOperation", checkGroupJson.getBoolean("isValidForOperation"))
                        .put("errorMessage", !checkGroupJson.getBoolean("isValidForOperation") ?
                          "Contact owner for group administration" : "");
                      ownerPromise.complete(groupJson);

                  }).subscribe().asCompletionStage();
            }
        }).subscribe().asCompletionStage();

        return ownerPromise.future();
    }

    protected Uni<List<String>> checkOnMembers(Group group, List<String> selectedList) {
        Promise<List<String>> listPromise = Promise.promise();
        List<String> newSelected = new ArrayList<>(selectedList);

        for (String userName : selectedList) {
            Set<Member> members = group.getMembers();
            if (members == null || members.isEmpty()) {
                listPromise.complete(selectedList);
                break;
            }
            Iterator<Member> membersIter = members.iterator();
            AtomicInteger counter = new AtomicInteger();
            membersIter.forEachRemaining(member -> {
                getUserByName(userName).onItemOrFailure().invoke((user, err) -> {
                    if (Objects.equals(user.getId(), member.getMemberId().getUser_id())) {
                        newSelected.remove(userName);
                    }
                    if (counter.incrementAndGet() == newSelected.size()) {
                        listPromise.complete(newSelected);
                    }
                }).subscribe().asCompletionStage();
            });
        }

        return listPromise.future();
    }

    protected JsonObject errData(Throwable err, JsonObject groupJson) {
        if (err != null && err.getMessage() != null) {
            groupJson
              .put("status", -1)
              .put("errorMessage", !err.getMessage().contains("batch execution") ?
                err.getMessage() : err.getMessage() + " -- some actions may have succeeded.");

            if (!err.getMessage().contains("batch execution")) {
                err.printStackTrace();
            } else {
                logger.error(err.getMessage());
            }
        }
        return groupJson;
    }

    public static Mutiny.SessionFactory getSessionFactory() {
        return GroupHibernateService.sessionFactory;
    }
}
