package com.arrebol.taxiservicios.controller.system;

import com.arrebol.taxiservicios.controller.connection.ConnectionManager;
import com.arrebol.taxiservicios.controller.queries.PermissionNQ;
import com.arrebol.taxiservicios.controller.queries.UserHasPermissionNQ;
import com.arrebol.taxiservicios.controller.util.HibernateUtil;
import com.arrebol.taxiservicios.model.core.Permission;
import com.arrebol.taxiservicios.model.core.User;
import com.arrebol.taxiservicios.model.enums.UserStatusType;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.CriteriaUpdate;
import javax.persistence.criteria.Root;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import com.arrebol.taxiservicios.controller.queries.UserNQ;
import com.arrebol.taxiservicios.model.catalog.Location;
import com.arrebol.taxiservicios.model.core.Person;
import com.arrebol.taxiservicios.model.core.UserHasPermission;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Set;

/**
 *
 * @author Carlos Janitzio Zavala Lopez
 */
public class UserHasPermissionController extends ConnectionManager implements Serializable {

    /**
     *
     * @param isPublicAccess True equals to Public Permission, False equals to
     * Private permission.
     *
     * @return Permission's List (Private or Public)
     */
    public List<Permission> findActivePermissionByPublicAccess(boolean isPublicAccess) {
        logger.info("findActivePermissionByPublicAccess");

        List<Permission> permissions = new ArrayList<>();
        Transaction transaction = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();

            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaQuery<Permission> query = builder.createQuery(Permission.class);
            Root<Permission> root = query.from(Permission.class);

            query.where(builder.and(
                    builder.equal(root.get("active"), Boolean.TRUE),
                    builder.equal(root.get("generalAccess"), isPublicAccess)
            ));

            query.orderBy(
                    builder.asc(root.get("leftToRightOrder")),
                    builder.asc(root.get("topToBottomOrder"))
            );

            permissions = session.createQuery(query).getResultList();

            transaction.commit();
            logger.info("Total of " + (isPublicAccess ? "public" : "private") + " permission loaded: " + permissions.size());
        } catch (HibernateException e) {
            logger.error("Permissions cannot be loaded", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method findActivePermissionByPublicAccess()", e);
            rollback(transaction);
        }

        return permissions;
    }

    /**
     *
     * @param userId
     * @param userHasPermissions If true, find all permission that user has, if
     * false, find all permissions that user does not have.
     * @return
     */
    public List<Permission> findPermissionByUserId(String userId, boolean userHasPermissions) {
        logger.info("findPermissionByUserId");

        List<Permission> permissions = new ArrayList<>();
        Transaction transaction = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();

            permissions = session.createNamedQuery(
                    userHasPermissions ? PermissionNQ.FIND_PERMISSION_BY_USER_ID : PermissionNQ.FIND_PERMISSION_THAT_USER_DOES_NOT_HAVE)
                    .setParameter(PermissionNQ.USER_FIELD, new User(userId, ""))
                    .setParameter(PermissionNQ.ACTIVE_FIELD, Boolean.TRUE)
                    .getResultList();

            transaction.commit();
            logger.info("Total of permission that user " + (userHasPermissions ? "has" : "hasn't") + " assigned loaded: " + permissions.size());
        } catch (HibernateException e) {
            logger.error("Permissions cannot be loaded", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method findPermissionByUserId(" + userId + ", " + userHasPermissions + ")", e);
            rollback(transaction);
        }

        return permissions;
    }

    /**
     * update the user password from UI.
     *
     * @param userId User identify number.
     * @param password Password to be updated.
     * @return true if password was updated otherwise false.
     */
    public boolean updatePassword(String userId, String password) {
        logger.info("updatePassword");

        boolean success = false;
        Transaction transaction = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();

            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaUpdate<User> update = builder.createCriteriaUpdate(User.class);

            Root<User> root = update.from(User.class);

            update.set("password", password);
            update.where(builder.equal(root.get("id"), userId));

            int total = session.createQuery(update).executeUpdate();

            if (1 == total) {
                logger.info("User updated: ");
                transaction.commit();

                success = true;
            } else {
                logger.error("User can not be updated ");
                rollback(transaction);
            }
        } catch (HibernateException e) {
            logger.error("User cannot be updated ", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method updatePassword(userId" + userId + ") ", e);
            rollback(transaction);
        }
        return success;
    }
    
    public boolean updateLocation(String userId, String locationId) {
        logger.info("updateLocation");

        boolean success = false;
        Transaction transaction = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();

            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaUpdate<User> update = builder.createCriteriaUpdate(User.class);

            Root<User> root = update.from(User.class);

            update.set("location", new Location(locationId));
            update.where(builder.equal(root.get("id"), userId));

            int total = session.createQuery(update).executeUpdate();

            if (1 == total) {
                logger.info("User updated: ");
                transaction.commit();

                success = true;
            } else {
                logger.error("User can not be updated ");
                rollback(transaction);
            }
        } catch (HibernateException e) {
            logger.error("User cannot be updated ", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method updateLocation(userId" + userId + ") ", e);
            rollback(transaction);
        }
        return success;
    }

   
    /**
     * 
     * @param userId
     * @param inUserStatusType
     * @return 
     */
    public List<User> findUsersNotRootByInStatus(String userId, List<UserStatusType> inUserStatusType, Location location) {
        logger.info("findUsersNotRootByInStatus");

        List<User> users = new ArrayList<>();
        Transaction transaction = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();

            users = session.createNamedQuery(UserNQ.FIND_USERS_NOT_ROOT_BY_IN_STATUS_QUERY, User.class)
                    .setParameter(UserNQ.USER_FIELD_IN_USER_STATUS_TYPE, inUserStatusType)
                    .setParameter(UserNQ.USER_LOCATION, location)
                    .setParameter(UserNQ.USER_FIELD_ID, userId)
                    .getResultList();

            transaction.commit();
            logger.info("Total of users " + users.size());
        } catch (HibernateException e) {
            logger.error("Users cannot be loaded", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method findUsersNotRootByInStatus()", e);
            rollback(transaction);
        }
        return users;
    }

    /**
     *
     * @param userIdToUpdateStatus Action (ENEBLED, DISEBLED or DELETED)
     * @param lastUpdatedBy
     * @param statusType ENEBLED, DISEBLED or DELETED
     * @return
     */
    public synchronized boolean changeUserStatusByStatus(String userIdToUpdateStatus,
            String lastUpdatedBy, UserStatusType statusType) {
        logger.info("changeUserStatusByStatus");

        boolean success = false;
        Transaction transaction = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();

            User user = session.get(User.class, userIdToUpdateStatus);

            if (UserStatusType.DELETED.equals(statusType)) {
                StringBuilder concatName = new StringBuilder(user.getUserName());
                SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

                concatName.append("-");
                concatName.append(sdf.format(new Date()));

                user.setUserName(concatName.toString());
                user.setPerson(new Person(USER_ID_MANAGER_APPLICATION));
            }

            user.setUserStatusType(statusType);
            user.setLastUpdatedBy(lastUpdatedBy);
            user.setLastUpdatedOn(new Date());

            session.update(user);

            if (UserStatusType.DELETED.equals(statusType)) {
                List<Boolean> accessType = new ArrayList<>();

                accessType.add(Boolean.FALSE);
                accessType.add(Boolean.TRUE);

                List<UserHasPermission> deleteList = session.createNamedQuery(UserHasPermissionNQ.QUERY_FIND_PERMISSION_BY_USER_ID_AND_GENERAL_ACCESS, UserHasPermission.class)
                        .setParameter(UserHasPermissionNQ.FIELD_USER, user)
                        .setParameter(PermissionNQ.GENERAL_ACCESS_FIELD, accessType)
                        .getResultList();

                deleteList.forEach((toDelete) -> {
                    session.delete(toDelete);
                });
            }

            transaction.commit();

            success = true;

            logger.info("User updated successfully as " + statusType.toString());
        } catch (HibernateException e) {
            logger.error("Users cannot be upload", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method changeUserStatusByStatus()", e);
            rollback(transaction);
        }

        return success;
    }

    /**
     *
     * @param generalAccess Get Public/Private permissions.
     * @param active Get all active permission.
     * @param applicationMenu If true get all permission taht are for navigate
     * in application (Menu), if false get all action grants like add, update,
     * delete.
     * @return List of pemission.
     */
    public List<Permission> findApplicationMenuPermissions(boolean generalAccess, boolean active, boolean applicationMenu) {
        logger.info("findApplicationMenuPermissions");

        List<Permission> appMenus = new ArrayList<>();
        Transaction transaction = null;

        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();

            appMenus = session.createNamedQuery(PermissionNQ.FIND_APPLICATION_MENU_PERMISSIONS, Permission.class)
                    .setParameter(PermissionNQ.GENERAL_ACCESS_FIELD, generalAccess)
                    .setParameter(PermissionNQ.ACTIVE_FIELD, active)
                    .setParameter(PermissionNQ.APPLICATION_MENU_FIELD, applicationMenu)
                    .getResultList();

            transaction.commit();
            logger.info("Total of application menu permissions " + appMenus.size());
        } catch (HibernateException e) {
            logger.error("Users cannot be upload", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method changeUserStatusByStatus()", e);
            rollback(transaction);
        }
        return appMenus;
    }

    /**
     *
     * @param userId User Id to get old permission.
     * @param newPermissionLst New permissions list for user.
     * @return true is success otherwise false.
     */
    public boolean updatePermissionsByUser(String userId, Set<UserHasPermission> newPermissionLst) {
        logger.info("updatePermissionsByUser");

        boolean success = false;
        Transaction transaction = null;

        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();

            List<Boolean> accessType = new ArrayList<>();

            accessType.add(Boolean.FALSE);

            List<UserHasPermission> oldPermissionLst = session.createNamedQuery(UserHasPermissionNQ.QUERY_FIND_PERMISSION_BY_USER_ID_AND_GENERAL_ACCESS, UserHasPermission.class)
                    .setParameter(UserHasPermissionNQ.FIELD_USER, new User(userId, ""))
                    .setParameter(PermissionNQ.GENERAL_ACCESS_FIELD, accessType)
                    .getResultList();

            oldPermissionLst.forEach((oldPermission) -> {
                session.delete(oldPermission);
            });

            newPermissionLst.forEach((newPermission) -> {
                session.save(newPermission);
            });

            transaction.commit();
            logger.info("Grants were updated.");
            success = true;
        } catch (HibernateException e) {
            logger.error("Users cannot be upload", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method updatePermissionsByUser()", e);
            rollback(transaction);
        }
        return success;
    }
    
    private static final long serialVersionUID = 3865580174324699885L;

    final Logger logger = LogManager.getLogger(UserHasPermissionController.class);
}
