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.PersonNQ;
import com.arrebol.taxiservicios.controller.queries.UserNQ;
import com.arrebol.taxiservicios.controller.util.HibernateUtil;
import com.arrebol.taxiservicios.model.admin.ClosingDay;
import com.arrebol.taxiservicios.model.catalog.Location;
import com.arrebol.taxiservicios.model.core.Permission;
import com.arrebol.taxiservicios.model.core.Person;
import com.arrebol.taxiservicios.model.core.User;
import com.arrebol.taxiservicios.model.core.UserHasPermission;
import com.arrebol.taxiservicios.model.core.UserHasPermissionId;
import com.arrebol.taxiservicios.model.enums.PersonType;
import com.arrebol.taxiservicios.model.enums.UserStatusType;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.CriteriaUpdate;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
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;

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

    /**
     *
     * @return
     */
    public List<Person> getAllEmployeeWithoutUser() {
        logger.info("getAllEmployeeWithoutUser: ");

        List<Person> employees = null;
        Transaction transaction = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();

            employees = session.createNamedQuery(PersonNQ.GET_ALL_EMPLOYEES_WITHOUT_USER_QUERY, Person.class)
                    .setParameter(PersonNQ.PERSON_FIELD_PERSON_TYPE, PersonType.EMPLOYEE)
                    .setParameter(PersonNQ.PERSON_FIELD_ID, USER_ID_MANAGER_APPLICATION)
                    .getResultList();

            transaction.commit();
        } catch (HibernateException e) {
            logger.error("Employees can not be loaded ", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method getAllEmployeeWithoutUser()", e);
            rollback(transaction);
        }
        return employees;
    }

    /**
     *
     * @param userName Nickname to find in User table.
     * @return True id nickname is not use by any user otherwise false.
     */
    public boolean availableUserName(String userName) {
        logger.info("availableUserName");

        boolean available = false;
        Transaction transaction = null;

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

            long total = (Long) session.createNamedQuery(UserNQ.AVAILABLE_USER_NAME_QUERY)
                    .setParameter(UserNQ.USER_FIELD_IN_USER_STATUS_TYPE, UserStatusType.ENABLED)
                    .setParameter(UserNQ.USER_FIELD_USER_NAME, userName)
                    .uniqueResult();

            if (total == 0) {
                available = true;
            }

            transaction.commit();
        } catch (HibernateException e) {
            logger.error("Available user name can not loaded", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method availableUserName()", e);
            rollback(transaction);
        }

        return available;
    }

    /**
     * Create a new user in next order: 1.- Go for current Person to get
     * details<br/>
     * 2.- Update Person (Avatar, Last updated by and Last updated on)<br/> 3.-
     * Save user.<br/>
     *
     * @param user
     * @param employeeId
     * @param avatar
     * @return
     */
    public synchronized boolean createUser(User user, String employeeId, String avatar) {
        logger.info("createUser");

        boolean success = false;
        Transaction transaction = null;

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

            Person person = session.get(Person.class, employeeId);

            person.setAvatar(avatar);
            person.setLastUpdatedBy(new User(user.getCreatedBy(), ""));
            person.setLastUpdatedOn(new Date());

            session.update(person);

            user.setPerson(person);

            session.save(user);

            List<Permission> permissions = session.createNamedQuery(PermissionNQ.FIND_PERMISSIONS_BY_PUBLIC_ACCESS, Permission.class)
                    .setParameter(PermissionNQ.GENERAL_ACCESS_FIELD, Boolean.TRUE)
                    .getResultList();

            permissions.forEach((permission) -> {
                UserHasPermissionId id = new UserHasPermissionId(user.getId(), permission.getId());
                UserHasPermission userHasPermission = new UserHasPermission(id);

                session.save(userHasPermission);
            });

            transaction.commit();

            success = true;

            logger.info("User was created");
        } catch (HibernateException e) {
            logger.error("User can not be created", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method createUser()", e);
            rollback(transaction);
        }
        return success;
    }

    public synchronized boolean createUserByLocation(User user, String employeeId, String avatar, String locationId) {
        logger.info("createUser");

        boolean success = false;
        Transaction transaction = null;

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

            Person person = session.get(Person.class, employeeId);
            Location location = session.get(Location.class, locationId);

            person.setAvatar(avatar);
            person.setLastUpdatedBy(new User(user.getCreatedBy(), ""));
            person.setLastUpdatedOn(new Date());

            session.update(person);

            user.setPerson(person);
            user.setLocation(location);

            session.save(user);

            List<Permission> permissions = session.createNamedQuery(PermissionNQ.FIND_PERMISSIONS_BY_PUBLIC_ACCESS, Permission.class)
                    .setParameter(PermissionNQ.GENERAL_ACCESS_FIELD, Boolean.TRUE)
                    .getResultList();

            permissions.forEach((permission) -> {
                UserHasPermissionId id = new UserHasPermissionId(user.getId(), permission.getId());
                UserHasPermission userHasPermission = new UserHasPermission(id);

                session.save(userHasPermission);
            });

            transaction.commit();

            success = true;

            logger.info("User was created");
        } catch (HibernateException e) {
            logger.error("User can not be created", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method createUser()", e);
            rollback(transaction);
        }
        return success;
    }
    
    public synchronized boolean createUserByLocationAllPermission(User user, String employeeId, String avatar, String locationId) {
        logger.info("createUser");

        boolean success = false;
        Transaction transaction = null;

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

            Person person = session.get(Person.class, employeeId);
            Location location = session.get(Location.class, locationId);

            person.setAvatar(avatar);
            person.setLastUpdatedBy(new User(user.getCreatedBy(), ""));
            person.setLastUpdatedOn(new Date());

            session.update(person);

            user.setPerson(person);
            user.setLocation(location);

            session.save(user);

            List<Permission> permissions = session.createNamedQuery(PermissionNQ.FIND_ALL_APPLICATION, Permission.class).getResultList();

            permissions.forEach((permission) -> {
                UserHasPermissionId id = new UserHasPermissionId(user.getId(), permission.getId());
                UserHasPermission userHasPermission = new UserHasPermission(id);

                session.save(userHasPermission);
            });

            transaction.commit();

            success = true;

            logger.info("User was created");
        } catch (HibernateException e) {
            logger.error("User can not be created", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method createUser()", e);
            rollback(transaction);
        }
        return success;
    }

    public boolean updateUserLocation(String idUser, String idLocation) {
        logger.info("updateUserLocation");

        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> rootUser = update.from(User.class);

            update.set("location", new Location(idLocation));

            update.where(builder.equal(rootUser.get("id"), idUser));

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

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

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

    public List<User> findUserActives(String idLocation) {
        logger.info("findUserActives");
        List<User> results = null;
        Transaction transaction = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();
            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaQuery<User> query = builder.createQuery(User.class);
            Root<User> root = query.from(User.class);

            Path<String> id = root.get("id");
            Path<Person> person = root.get("person");
            Path<String> userName = root.get("userName");

            Predicate activeStatusPredicate = builder.equal(root.get("active"), Boolean.TRUE);
            Predicate locationPredicate = builder.equal(root.get("location"), new Location(idLocation));

            query.select(
                    builder.construct(
                            User.class,
                            id, userName, person
                    )
            );
            query.where(builder.and(activeStatusPredicate, locationPredicate));
            query.orderBy(builder.asc(root.get("userName")));

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

            transaction.commit();

            logger.info("User list " + results.size());
        } catch (HibernateException e) {
            logger.error("Can not find user", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method findUserActives()", e);
            rollback(transaction);
        }
        return results;
    }
    
    public List<User> findAllUserActives() {
        logger.info("findAllUserActives");
        List<User> results = null;
        Transaction transaction = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();
            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaQuery<User> query = builder.createQuery(User.class);
            Root<User> root = query.from(User.class);

            Path<String> id = root.get("id");
            Path<Person> person = root.get("person");
            Path<String> userName = root.get("userName");

            Predicate activeStatusPredicate = builder.equal(root.get("active"), Boolean.TRUE);

            query.select(builder.construct(User.class,id, userName, person));
            query.where(builder.and(activeStatusPredicate));
            query.orderBy(builder.asc(root.get("userName")));

            results = session.createQuery(query).getResultList();
            transaction.commit();
            logger.info("User list " + results.size());
        } catch (HibernateException e) {
            logger.error("Can not find user", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method findAllUserActives()", e);
            rollback(transaction);
        }
        return results;
    }

    public User getById(String userId) {
        logger.info("getById");

        User sanction = null;
        Transaction transaction = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();

            sanction = session.get(User.class, userId);

            transaction.commit();
            logger.info("User " + sanction);
        } catch (HibernateException e) {
            logger.error("User cannot be loaded " + userId, e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method getById(" + userId + ") ", e);
            rollback(transaction);
        }
        return sanction;
    }
    
    public User getByUserNameAndPassword(String name, String password) {
         logger.info("getByUserNameAndPassword");
        User results = null;
        Transaction transaction = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();
            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaQuery<User> query = builder.createQuery(User.class);
            Root<User> root = query.from(User.class);

            Path<String> id = root.get("id");
            Path<Person> person = root.get("person");
            Path<String> userName = root.get("userName");
            Path<Boolean>  root2 = root.get("root");
            Path<Location> location = root.get("location");

            Predicate activeStatusPredicate = builder.equal(root.get("active"), Boolean.TRUE);
            Predicate valName = builder.equal(root.get("userName"), name);
            Predicate valPassword = builder.equal(root.get("password"), password);
            query.select(
                    builder.construct(
                            User.class,
                            id, userName, person, root2, location
                    )
            );
            query.where(builder.and(activeStatusPredicate, valName, valPassword));
            query.orderBy(builder.asc(root.get("userName")));
            results = session.createQuery(query).getSingleResult();
            transaction.commit();
        } catch (HibernateException e) {
            logger.error("Can not find user", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method findUserActives()", e);
            rollback(transaction);
        }
        return results;
    }
    
    public User getByUserName(String name) {
         logger.info("getByUserName");
        User results = null;
        Transaction transaction = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();
            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaQuery<User> query = builder.createQuery(User.class);
            Root<User> root = query.from(User.class);

            Path<String> id = root.get("id");
            Path<Person> person = root.get("person");
            Path<String> userName = root.get("userName");
            Path<Boolean>  root2 = root.get("root");
            Path<Location> location = root.get("location");

            Predicate activeStatusPredicate = builder.equal(root.get("active"), Boolean.TRUE);
            Predicate valName = builder.equal(root.get("userName"), name);
            query.select(
                    builder.construct(
                            User.class,
                            id, userName, person, root2, location
                    )
            );
            query.where(builder.and(activeStatusPredicate, valName));
            query.orderBy(builder.asc(root.get("userName")));
            results = session.createQuery(query).getSingleResult();
            transaction.commit();
        } catch (HibernateException e) {
            logger.error("Can not find user", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method findUserActives()", e);
            rollback(transaction);
        }
        return results;
    }
    

    /**
     *
     * @param closingDay
     * @return
     */
    public boolean addClosingDateToNewUser(ClosingDay closingDay) {
        logger.info("addClosingDateToNewUser");

        boolean success = false;
        Transaction transaction = null;

        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();
            closingDay.setAmountTransfer(BigDecimal.ZERO);

            session.save(closingDay);
            transaction.commit();

            success = true;

            logger.info("Closing date was created for new user");
        } catch (HibernateException e) {
            logger.error("Add Closing Date To New User was not created", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method addClosingDateToNewUser()", e);
            rollback(transaction);
        }
        return success;
    }
    
    public User getByPerson(Person personb) {
         logger.info("getByUserName");
        User results = null;
        Transaction transaction = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();
            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaQuery<User> query = builder.createQuery(User.class);
            Root<User> root = query.from(User.class);

            Path<String> id = root.get("id");
            Path<Person> person = root.get("person");
            Path<String> userName = root.get("userName");
            Path<Boolean>  root2 = root.get("root");
            Path<Location> location = root.get("location");

            Predicate activeStatusPredicate = builder.equal(root.get("active"), Boolean.TRUE);
            Predicate valName = builder.equal(root.get("person"), personb);
            
            query.select(builder.construct(User.class,id, userName, person, root2, location));
            query.where(builder.and(activeStatusPredicate, valName));
            query.orderBy(builder.asc(root.get("userName")));
            
            results = session.createQuery(query).getSingleResult();
            transaction.commit();
        } catch (HibernateException e) {
            logger.error("Can not find user", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method findUserActives()", e);
            rollback(transaction);
        }
        return results;
    }

    private static final long serialVersionUID = -3316029744823724815L;

    final Logger logger = LogManager.getLogger(UserController.class);

}
