/*
 * Arrebol Consultancy copyright.
 * 
 * This code belongs to Arrebol Consultancy
 * its use, redistribution or modification are prohibited 
 * without written authorization from Arrebol Consultancy.
 */
package com.arrebol.taxiservicios.controller.mutual;

import com.arrebol.taxiservicios.controller.connection.ConnectionManager;
import com.arrebol.taxiservicios.controller.util.HibernateUtil;
import com.arrebol.taxiservicios.model.catalog.Location;
import com.arrebol.taxiservicios.model.core.Person;
import com.arrebol.taxiservicios.model.core.User;
import com.arrebol.taxiservicios.model.enums.BasicType;
import com.arrebol.taxiservicios.model.enums.UserStatusType;
import com.arrebol.taxiservicios.model.mutual.MutualClosingDay;
import com.arrebol.taxiservicios.model.mutual.MutualClosingDayDetail;
import com.arrebol.taxiservicios.model.mutual.MutualStableSmallBox;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import javax.faces.view.ViewScoped;
import javax.inject.Named;
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;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.Query;

/**
 *
 * @author Oscar Vargas
 */
public class MutualClosingDayController extends ConnectionManager implements Serializable {

    /**
     *
     * @param date
     * @return
     */
    public boolean existNextPaidMutualClosingDayByCreatedOn(Date date) {
        logger.info("existNextPaidMutualClosingDayByCreatedOn");

        boolean success = true;
        Transaction transaction = null;

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

            String query = "SELECT cd.id "
                    + "FROM MUTUAL_CLOSING_DAY cd "
                    + "WHERE DATE(cd.created_on) >= DATE_ADD(DATE(:date), INTERVAL 1 DAY) "
                    + "AND cd.active_status = 'ENEBLED'";

            List<String> records = session.createSQLQuery(query).setParameter("date", date).getResultList();

            if (records.isEmpty()) {
                success = false;
            }

            transaction.commit();

            logger.info("Total of mutual closing day found: " + records.size());
        } catch (HibernateException e) {
            logger.error("Can not find next mutual closing day by created on", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method existNextPaidMutualClosingDayByCreatedOn()", e);
            rollback(transaction);
        }

        return success;
    }

    /**
     *
     * @return
     */
    public List<User> getAllActiveUsers() {
        logger.info("getAllActiveUsers");

        List<User> users = new ArrayList<>();
        Transaction transaction = null;

        try {
            List<User> usersAlreadyClosedDay = getAllUsersWithMutualClasingDay();
            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");

            Predicate userStatusTypePredicate = builder.equal(root.get("userStatusType"), UserStatusType.ENABLED);

            query.select(
                    builder.construct(User.class, id, person)
            );

            if (usersAlreadyClosedDay.isEmpty()) {
                query.where(userStatusTypePredicate);
            } else {
                CriteriaBuilder.In<String> ids = builder.in(root.get("id"));
                usersAlreadyClosedDay.forEach(user -> {
                    ids.value(user.getId());
                });
                query.where(userStatusTypePredicate, builder.not(ids));
            }

            query.orderBy(builder.asc(root.get("person")));

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

            transaction.commit();

            logger.info("Employe list " + users.size());
        } catch (HibernateException e) {
            logger.error("Can not find acive users", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method getAllActiveUsers()", e);
            rollback(transaction);
        }
        return users;
    }

    /**
     * Find Closing daily's list between Dates (Start date and End Date).
     *
     * @param mutualStableSmallBox
     * @param startDate
     * @param endDate
     * @return
     */

    public Date lastMutualStableSmallBoxBySmallBox(MutualStableSmallBox mutualStableSmallBox) {
        logger.info("lastStableSmallBoxByDate");

        Date success = null;
        Transaction transaction = null;

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

            String query = "SELECT cd.createdOn FROM MutualStableSmallBox cd "
                    + "WHERE DATE(cd.createdOn) < DATE(:date) AND cd.location.id = :locationID AND cd.activeStatus = 'ENEBLED' "
                    + "order by cd.createdOn DESC";

            success = (Date) session.createQuery(query)
                    .setParameter("date", mutualStableSmallBox.getCreatedOn())
                    .setParameter("locationID", mutualStableSmallBox.getLocation().getId()).setMaxResults(1)
                    .uniqueResult();

            transaction.commit();

            logger.info("Total of closing day found: ");
        } catch (HibernateException e) {
            logger.error("Can not find mutual stable small box by created on", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method lastMutualStableSmallBoxByDate()", e);
            rollback(transaction);
        }

        return success;
    }

    public List<MutualClosingDay> findMutualClosingDailyListBetweenDates(Date startDate, Date endDate, String idLocation) {
        logger.info("findMutualClosingDailyListBetweenDates");

        List<MutualClosingDay> results = null;
        Transaction transaction = null;

        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();
            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaQuery<MutualClosingDay> query = builder.createQuery(MutualClosingDay.class);
            Root<MutualClosingDay> root = query.from(MutualClosingDay.class);

            Path<String> id = root.get("id");
            Path<User> user = root.get("user");
            Path<BigDecimal> amountPaid = root.get("amountPaid");
            Path<BigDecimal> amountExpected = root.get("amountExpected");
            Path<String> comments = root.get("comments");
            Path<Date> createdOn = root.get("createdOn");
            Path<User> createdByPath = root.get("createdBy");
            Path<Location> locationPath = root.get("location");

            Predicate activeStatusPredicate = builder.equal(root.get("activeStatus"), BasicType.ENEBLED);
            Predicate startDatePredicate = builder.greaterThan(root.get("createdOn"), startDate);
            Predicate endDatePredicate = builder.lessThanOrEqualTo(root.get("createdOn"), endDate);
            Predicate locationPredicate = builder.equal(root.get("location"), new Location(idLocation));

            query.select(
                    builder.construct(
                            MutualClosingDay.class, id, user,
                            amountPaid, amountExpected, comments, createdOn, createdByPath, locationPath
                    )
            );
            query.where(builder.and(activeStatusPredicate, startDatePredicate, endDatePredicate, locationPredicate));
            query.orderBy(builder.asc(root.get("createdOn")));

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

            transaction.commit();

            logger.info("Mutual Closing daily's list " + results.size());
        } catch (HibernateException e) {
            logger.error("Can not find mutual closing daily list between dates", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method findMutualClosingDailyListBetweenDates()", e);
            rollback(transaction);
        }
        return results;
    }

    public List<MutualClosingDay> findMutualClosingDailyCurdate(String idLocation) {
        logger.info("findMutualClosingDailyCurdate");

        List<MutualClosingDay> results = null;
        Transaction transaction = null;

        Date fecha = getLastDateStableSmallBox(idLocation);

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

            transaction = session.beginTransaction();

            results = session.createQuery("FROM MutualClosingDay c WHERE c.activeStatus = 'ENEBLED' AND c.location.id = :idLocation AND DATE(c.createdOn) > :fecha ")
                    .setParameter("idLocation", idLocation)
                    .setParameter("fecha", fecha)
                    .getResultList();

            transaction.commit();

            logger.info("Mutual Closing daily detail's list " + results.size());
        } catch (HibernateException e) {
            logger.error("Can not find mutual closing daily details list", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method findMutualClosingDailyCurdate()", e);
            rollback(transaction);
        }
        return results;
    }

    public Date getLastDateStableSmallBox(String locationId) {
        logger.info("getLastDateStableSmallBox");

        Date total = new Date();
        Transaction transaction = null;
        logger.info(locationId);

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

            String sqlQuery = "SELECT DATE(sgb.created_on) FROM STABLE_SMALL_BOX sgb WHERE DATE(sgb.created_on) < CURDATE() AND sgb.id_location = '" + locationId + "' AND sgb.active_status = 'ENEBLED' ORDER BY sgb.created_on DESC LIMIT 1";

            NativeQuery nativeQuery = session.createSQLQuery(sqlQuery);

            total = (Date) (nativeQuery.uniqueResult());

            logger.info(total);
            transaction.commit();
        } catch (HibernateException e) {
            logger.error("Can not load the last date in stable small box", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method getLastDateStableSmallBox()", e);
            rollback(transaction);
        }

        return total;
    }

    public List<MutualClosingDayDetail> findDetailsFromMutualClosingDayID(MutualClosingDay closingDay) {
        logger.info("findDetailsFromMutualClosingDayID");

        List<MutualClosingDayDetail> results = null;
        Transaction transaction = null;

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

            transaction = session.beginTransaction();

            results = session.createQuery("FROM MutualClosingDayDetail WHERE closingDay = :closingDay ORDER BY type, person")
                    .setParameter("closingDay", closingDay)
                    .getResultList();

            transaction.commit();

            logger.info("Mutual Closing daily detail's list " + results.size());
        } catch (HibernateException e) {
            logger.error("Can not find mutual closing daily details list", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method findDetailsFromMutualClosingDayID()", e);
            rollback(transaction);
        }
        return results;
    }

    /**
     * Update active status (ENEBLED/DISABLED) of closing day table by ID.
     *
     * @param id Primary key to be updated.
     * @param status Status to be updated (ENEBLED/DISABLED).
     * @param lastUpdatedBy User to apply update.
     * @return
     */
    public boolean updateMutualClosingDailyById(String id, BasicType status, User lastUpdatedBy) {
        logger.info("updateMutualClosingDailyById");

        boolean success = false;
        Transaction transaction = null;

        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();
            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaUpdate<MutualClosingDay> query = builder.createCriteriaUpdate(MutualClosingDay.class);
            Root<MutualClosingDay> root = query.from(MutualClosingDay.class);

            query.set(root.get("activeStatus"), status);
            query.set(root.get("lastUpdatedBy"), lastUpdatedBy);
            query.set(root.get("lastUpdatedOn"), new Date());

            query.where(builder.equal(root.get("id"), id));

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

            if (1 == total) {
                transaction.commit();
                success = true;
                logger.info("Update mutual closing day status successfully");
            } else {
                logger.info("CAN NOT update mutual closing day");
                rollback(transaction);
            }
        } catch (HibernateException e) {
            logger.info("CAN NOT update mutual closing day", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method updateMutualClosingDailyById()", e);
            rollback(transaction);
        }
        return success;
    }

    /**
     *
     * @param sqlQuery
     * @param userID
     * @return
     */
    public Double findTotals(String sqlQuery, String userID) {
        logger.info("findTotals");

        Double total = null;
        Transaction transaction = null;

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

            NativeQuery nativeQuery = session.createSQLQuery(sqlQuery);

            nativeQuery.setParameter("userID", userID);

            total = (Double) (nativeQuery.uniqueResult());

            transaction.commit();
        } catch (HibernateException e) {
            logger.error("Can not execute sql native query", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method findTotals()", e);
            rollback(transaction);
        }

        return total;
    }

    public BigDecimal findTotalsBigDecimal(String sqlQuery, String userID) {
        logger.info("findTotals");

        BigDecimal total = null;
        Transaction transaction = null;

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

            NativeQuery nativeQuery = session.createSQLQuery(sqlQuery);

            nativeQuery.setParameter("userID", userID);

            total = (BigDecimal) (nativeQuery.uniqueResult());

            transaction.commit();
        } catch (HibernateException e) {
            logger.error("Can not execute sql native query", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method findTotals()", e);
            rollback(transaction);
        }

        return total;
    }

    /**
     *
     * @param closingDay
     * @param details
     * @return
     */
    public boolean saveMutualClosingDayAndDetails(MutualClosingDay closingDay, List<MutualClosingDayDetail> details) {
        logger.info("saveMutualClosingDayAndDetails");

        boolean success = false;
        Transaction transaction = null;

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

            session.save(closingDay);

            details.forEach(detail -> {
                session.save(detail);
            });

            transaction.commit();
            success = true;
        } catch (HibernateException e) {
            logger.error("Can not save mutual Closing Day And Details", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method saveMutualClosingDayAndDetails()", e);
            rollback(transaction);
        }
        return success;
    }

    /**
     *
     * @param userID
     * @return
     */
    public List<MutualClosingDayDetail> findMutualClosingDayDetails(String userID) {
        List<MutualClosingDayDetail> details = new ArrayList<>();
        try {
            details.addAll(buildDetailsList(1, executeNativeSQLQuery(query_details_transfer_receiver, userID)));
            details.addAll(buildDetailsList(2, executeNativeSQLQuery(query_details_sanctions_in, userID)));
            details.addAll(buildDetailsList(3, executeNativeSQLQuery(query_details_sanctions_out, userID)));
            details.addAll(buildDetailsList(4, executeNativeSQLQuery(query_details_inicios, userID)));

        } catch (Exception e) {
            logger.error(e);
        }
        return details;
    }

    /**
     *
     * @return
     */
    private List<User> getAllUsersWithMutualClasingDay() {
        List<User> users = new ArrayList<>();
        Transaction transaction = null;

        try {
            String hql = "SELECT c From MutualClosingDay c WHERE DATE(c.createdOn) = CURDATE() AND c.activeStatus = 'ENEBLED'";
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();

            Query query = session.createQuery(hql);
            List<MutualClosingDay> results = query.getResultList();

            results.forEach((MutualClosingDay closingDay) -> {
                users.add(closingDay.getUser());
            });

            transaction.commit();
        } catch (HibernateException e) {
            logger.error("Can not save mutual Closing Day And Details", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method getAllUsersWithMutualClasingDay()", e);
            rollback(transaction);
        }
        return users;
    }

    /**
     *
     * @param queryNumber 1: query_details_sales , 2: query_details_sanctions_in
     * , 3: query_details_sanctions_out , 4: query_details_expenses , 5:
     * query_details_invoices.
     * @param results
     * @return
     */
    private List<MutualClosingDayDetail> buildDetailsList(int queryNumber, Object results) {
        List<MutualClosingDayDetail> details = new ArrayList<>();
        try {
            List<?> list = new ArrayList<>();
            if (null != results) {
                if (results.getClass().isArray()) {
                    list = Arrays.asList((Object[]) results);
                } else if (results instanceof Collection) {
                    list = new ArrayList<>((Collection<?>) results);
                }

                for (Object object : list) {
                    Object[] array = (Object[]) object;

                    MutualClosingDayDetail closingDayDetail = new MutualClosingDayDetail(queryNumber, array);
                    details.add(closingDayDetail);
                }
                logger.info("execute sql native query's list " + details.size());
            }
        } catch (Exception e) {
            logger.error(e);
        }
        return details;
    }

    /**
     *
     * @param sqlQuery
     * @param userID
     * @return
     */
    private Object executeNativeSQLQuery(String sqlQuery, String userID) {
        Object results = null;
        Transaction transaction = null;

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

            NativeQuery nativeQuery = session.createSQLQuery(sqlQuery);

            nativeQuery.setParameter("userID", userID);

            results = nativeQuery.getResultList();

            transaction.commit();
        } catch (HibernateException e) {
            logger.error("Can not execute sql native query", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method executeNativeSQLQuery()", e);
            rollback(transaction);
        }

        return results;
    }

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

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

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

            transaction.commit();
            logger.info("User " + general);
        } catch (HibernateException e) {
            logger.error("User cannot be loaded " + userId, e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method getUserById(" + userId + ") ", e);
            rollback(transaction);
        }
        return general;
    }

    private final String complete_name = "CONCAT(\n"
            + "                    CASE\n"
            + "                        WHEN p.first_name IS NOT NULL AND p.first_name != ''\n"
            + "                        THEN CONCAT(SUBSTR(UPPER(p.first_name), 1, 1),SUBSTR(LOWER(p.first_name), 2), ' ')\n"
            + "                        ELSE ''\n"
            + "                    END,\n"
            + "                    CASE\n"
            + "                        WHEN p.second_name IS NOT NULL AND p.second_name != ''\n"
            + "                        THEN CONCAT(SUBSTR(UPPER(p.second_name), 1, 1),SUBSTR(LOWER(p.second_name), 2), ' ')\n"
            + "                        ELSE ''\n"
            + "                    END, \n"
            + "                    CASE\n"
            + "                        WHEN p.last_name IS NOT NULL AND p.last_name != ''\n"
            + "                        THEN CONCAT(SUBSTR(UPPER(p.last_name), 1, 1),SUBSTR(LOWER(p.last_name), 2), ' ')\n"
            + "                        ELSE ''\n"
            + "                    END,\n"
            + "                    CASE\n"
            + "                        WHEN p.middle_name IS NOT NULL AND p.middle_name != ''\n"
            + "                        THEN CONCAT(SUBSTR(UPPER(p.middle_name), 1, 1),SUBSTR(LOWER(p.middle_name), 2))\n"
            + "                        ELSE ''\n"
            + "                    END\n"
            + "                ) AS person_name";
    private final String sub_query_to_find_previously_closing_day = "(SELECT DATE(cd.created_on) FROM MUTUAL_CLOSING_DAY cd WHERE DATE(cd.created_on) <= CURDATE() AND cd.user_id = :userID AND cd.active_status = 'ENEBLED' order by cd.created_on DESC LIMIT 1)";
    private final String query_details_sanctions_in = "SELECT s.payment, s.mutual_add_type, s.driver_id, s.description, " + complete_name + " FROM MUTUAL_ADD s LEFT JOIN PERSON p ON s.driver_id = p.id WHERE DATE(s.created_on)  > " + sub_query_to_find_previously_closing_day + " AND s.created_by = :userID";
    private final String query_details_sanctions_out = "SELECT s.payment, s.mutual_out_type, s.driver_id, s.description, " + complete_name + " FROM MUTUAL_OUT s LEFT JOIN PERSON p ON s.driver_id = p.id WHERE DATE(s.created_on)  > " + sub_query_to_find_previously_closing_day + "  AND s.created_by = :userID";
    private final String query_details_transfer_receiver = "SELECT i.amount_to_transfer, u.person_id, '' as comment, " + complete_name + " FROM TRANSFER_MUTUAL i "
            + "INNER JOIN CAR_DRIVER_USER u ON i.id_user_transmitter = u.id INNER JOIN PERSON p ON u.person_id = p.id "
            + "WHERE DATE(i.created_on)  > " + sub_query_to_find_previously_closing_day + "  AND i.action_status = 'APPROVED' AND i.active_status = 'ENEBLED' "
            + "AND i.id_user_receiver = :userID";
    private final String query_details_inicios = "SELECT s.amount, u.person_id, s.description, " + complete_name + " "
            + "FROM MUTUAL_INICIOS s INNER JOIN CAR_DRIVER_USER u on s.user_id = u.id "
            + "INNER JOIN PERSON p ON p.id = u.person_id "
            + "WHERE DATE(s.created_on)  > " + sub_query_to_find_previously_closing_day + " AND s.user_id = :userID AND s.active_status = 'ENEBLED'";

    final Logger logger = LogManager.getLogger(MutualClosingDayController.class);
    private static final long serialVersionUID = -6732331411572526429L;
}
