/*
 * 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.stocktaking;

import com.arrebol.taxiservicios.controller.connection.ConnectionManager;
import com.arrebol.taxiservicios.controller.queries.UserNQ;
import com.arrebol.taxiservicios.controller.util.DateWrapper;
import com.arrebol.taxiservicios.controller.util.HibernateUtil;
import com.arrebol.taxiservicios.model.admin.Taxi;
import com.arrebol.taxiservicios.model.catalog.Location;
import com.arrebol.taxiservicios.model.catalog.Product;
import com.arrebol.taxiservicios.model.core.Person;
import com.arrebol.taxiservicios.model.core.User;
import com.arrebol.taxiservicios.model.enums.ServiceOrderType;
import com.arrebol.taxiservicios.model.enums.StopNotificationType;
import com.arrebol.taxiservicios.model.stocktaking.ServiceOrder;
import com.arrebol.taxiservicios.model.stocktaking.ServiceOrderHasProduct;
import com.arrebol.taxiservicios.model.stocktaking.ServiceOrderNotification;
import com.arrebol.taxiservicios.model.stocktaking.ServiceOrderNotificationDeliveryTo;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import javax.enterprise.context.SessionScoped;
import javax.persistence.Tuple;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaDelete;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.CriteriaUpdate;
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.Query;
//SELECT pendingDate AS startDate, CASE WHEN serviceOrderType IN ('PENDING','APPROVED','PROGRESS') THEN totalDaysInARangeOfDates(pendingDate, CURDATE(), false) WHEN serviceOrderType = 'FINISHED' THEN totalDaysInARangeOfDates(pendingDate, finishDate, false) END AS totalOfDay FROM com.arrebol.taxiservicios.model.stocktaking.ServiceOrder WHERE serviceOrderType IN ('PENDING','APPROVED','PROGRESS','FINISHED') AND CASE WHEN DATE(pendingDate) BETWEEN DATE(:startDate) AND DATE(:endDate) THEN 1 = 1 WHEN DATE(approvateDate) BETWEEN DATE(:startDate) AND DATE(:endDate) THEN 1 = 1 WHEN DATE(progressDate) BETWEEN DATE(:startDate) AND DATE(:endDate) THEN 1 = 1 WHEN DATE(finishDate) BETWEEN DATE(:startDate) AND DATE(:endDate) THEN 1 = 1 ELSE 1 = 0 END AND taxi = :taxi

/**
 *
 * @author Carlos Janitzio Zavala Lopez <janitzio.zavala@arrebol.com.mx>
 */
@SessionScoped
public class ServiceOrderServiceImpl extends ConnectionManager implements ServiceOrderService, Serializable {

    @Override
    public synchronized boolean workWithServiceOrder(int operation, String orderId, List<ServiceOrderHasProduct> orderHasProducts, ServiceOrder so, String userId, String taxiId, Float km, Float kmMaintenance, Location location) {
        boolean success = false;
        Transaction transaction = null;

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

            transaction = session.beginTransaction();

            ServiceOrder serviceOrder = session.get(ServiceOrder.class, orderId);

            transaction.commit();

            if (null != serviceOrder) {
                switch (operation) {
                    case 1:
                        if (ServiceOrderType.PENDING.equals(serviceOrder.getServiceOrderType())) {
                            success = updateServiceOrderProducts(orderHasProducts, so);
                        }
                        break;
                    case 2:
                        if (ServiceOrderType.PENDING.equals(serviceOrder.getServiceOrderType())) {
                            success = authorizeServiceOrder(orderId, userId, taxiId, km, orderHasProducts, kmMaintenance, location);
                        }
                        break;
                    case 3:
                        if (ServiceOrderType.PENDING.equals(serviceOrder.getServiceOrderType())) {
                            success = deleteServiceOrder(orderId);
                        }
                        break;
                    case 4:
                        if (ServiceOrderType.APPROVED.equals(serviceOrder.getServiceOrderType())) {
                            success = updateStatus(orderId, ServiceOrderType.PROGRESS, userId);
                        }
                        break;
                    case 5:
                        if (ServiceOrderType.PROGRESS.equals(serviceOrder.getServiceOrderType())) {
                            success = updateStatus(orderId, ServiceOrderType.FINISHED, userId);
                        }
                    case 6:
                        if (ServiceOrderType.PASE_TALLER.equals(serviceOrder.getServiceOrderType())) {
                            success = updateStatus(orderId, ServiceOrderType.PENDING, userId);
                        }
                        break;
                }
            }
        } catch (HibernateException e) {
            rollback(transaction);
            logger.error("Cannot workWithServiceOrder " + e);
        } catch (Exception e) {
            rollback(transaction);
            logger.error("Method workWithServiceOrderList", e);
        }
        return success;
    }

    @Override
    public synchronized void saveServiceOrderNotification(ServiceOrderNotification notification) {
        logger.debug("saveServiceOrderNotification");

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

            session.save(notification);

            transaction.commit();
            logger.debug("Notification created successfully");
        } catch (HibernateException e) {
            rollback(transaction);
            logger.error("Cannot create a notification " + e);
        } catch (Exception e) {
            rollback(transaction);
            logger.error("Method saveServiceOrderNotification", e);
        }
    }

    @Override
    public synchronized List<String> findAllUsersByPermissionHowWillReciveNotification(String userId, List<String> permissionLst, StopNotificationType stopNotificationType) {
        logger.debug("findAllUsersByPermissionHowWillReciveNotification");

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

            usrLst = session.createNamedQuery(UserNQ.QUERY_FIND_ALL_USERS_TO_NOTIFIED)
                    .setParameter(UserNQ.USER_FIELD_ID, userId)
                    .setParameter(UserNQ.PARAM_PERMISSION_LIST, permissionLst)
                    .setParameter(UserNQ.PARAM_STOP_NOTIFICATION_TYPE, stopNotificationType)
                    .getResultList();

            transaction.commit();
            logger.debug("Total of user notifications total: " + usrLst.size());
        } catch (HibernateException e) {
            rollback(transaction);
            logger.error("Cannot find a notifiaction users" + e);
        } catch (Exception e) {
            rollback(transaction);
            logger.error("Method findAllUsersByPermissionHowWillReciveNotification", e);
        }
        return usrLst;
    }

    @Override
    public synchronized void sendNotifications(List<ServiceOrderNotificationDeliveryTo> deliveryToLst) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    /**
     *
     * @param sohps
     * @param order
     * @return
     */
    private boolean updateServiceOrderProducts(List<ServiceOrderHasProduct> sohps,
            ServiceOrder order) {
        logger.debug("updateServiceOrderProducts");

        boolean success = false;
        Transaction transaction = null;

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

            transaction = session.beginTransaction();

            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaDelete<ServiceOrderHasProduct> delete = builder.createCriteriaDelete(ServiceOrderHasProduct.class);
            Root<ServiceOrderHasProduct> root = delete.from(ServiceOrderHasProduct.class);

            delete.where(builder.equal(root.get("serviceOrder"), new ServiceOrder(order.getId())));

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

            if (total >= 0) {
                logger.debug("Service order has deleted total or assigned products: " + total);
                ServiceOrder orderToUpdate = session.get(ServiceOrder.class, order.getId());

                orderToUpdate.setSubtotal(order.getSubtotal());
                orderToUpdate.setIva(order.getIva());
                orderToUpdate.setDescription(order.getDescription());
                orderToUpdate.setTotal(order.getTotal());
                orderToUpdate.setPendingDate(order.getPendingDate());
                orderToUpdate.setLastUpdatedBy(order.getLastUpdatedBy());
                orderToUpdate.setLastUpdatedOn(order.getLastUpdatedOn());

                session.update(orderToUpdate);

                sohps.forEach(sohp -> {
                    session.save(sohp);
                });

                transaction.commit();
                logger.debug("Service order has products updated");

                success = true;
            } else {
                rollback(transaction);
                logger.error("No product deleted for service order: " + order.getId());
            }

        } catch (HibernateException e) {
            rollback(transaction);
            logger.error("Cannot update products in Service order " + order.getId(), e);
        } catch (Exception e) {
            rollback(transaction);
            logger.error("Method updateServiceOrderProducts(List<ServiceOrderHasProduct> " + sohps + ", ServiceOrder " + order + ')', e);
        }

        return success;
    }

    /**
     *
     * @param orderId Service Order Id.
     * @param userId User Id.
     * @param taxiId Taxi Id.
     * @param km Kilometers for new service order.
     * @param products Listo of products.
     *
     * @return
     */
    private boolean authorizeServiceOrder(String orderId, String userId,
            String taxiId, Float km, List<ServiceOrderHasProduct> products, Float kmMaintenance, Location location) {
        logger.debug("authorizeServiceOrder");

        boolean success = false;
        User user = new User(userId, "");
        Transaction transaction = null;

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

            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaUpdate<Taxi> updateTaxi = builder.createCriteriaUpdate(Taxi.class);
            Root<Taxi> rootTaxi = updateTaxi.from(Taxi.class);

            updateTaxi.set(rootTaxi.get("km"), km);
            updateTaxi.set(rootTaxi.get("kmMaintenance"), kmMaintenance);
            updateTaxi.set(rootTaxi.get("lastUpdatedBy"), user);
            updateTaxi.set(rootTaxi.get("lastUpdatedOn"), DateWrapper.getTodayMXTime());

            updateTaxi.where(builder.equal(rootTaxi.get("id"), taxiId));

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

            if (1 == total) {
                logger.debug("Taxi was updated.");

                CriteriaUpdate<Product> updateProduct = builder.createCriteriaUpdate(Product.class);
                Root<Product> rootProduct = updateProduct.from(Product.class);

                if (location.getId().equalsIgnoreCase("4d0859be-e31a-4acb-a690-2a5c2cb027a7")) {
                    products.forEach(p -> {
                        updateProduct.set(rootProduct.get("quantity"), (p.getProduct().getQuantity() - p.getQuantity()));
                        updateProduct.set(rootProduct.get("lastUpdatedBy"), user);
                        updateProduct.set(rootProduct.get("lastUpdatedOn"), DateWrapper.getTodayMXTime());

                        updateProduct.where(builder.equal(rootProduct.get("id"), p.getProduct().getId()));

                        session.createQuery(updateProduct).executeUpdate();
                        logger.debug("Product stock was updated.");
                    });
                } else {
                    products.forEach(p -> {
                        updateProduct.set(rootProduct.get("quantity2"), (p.getProduct().getQuantity2() - p.getQuantity()));
                        updateProduct.set(rootProduct.get("lastUpdatedBy"), user);
                        updateProduct.set(rootProduct.get("lastUpdatedOn"), DateWrapper.getTodayMXTime());

                        updateProduct.where(builder.equal(rootProduct.get("id"), p.getProduct().getId()));

                        session.createQuery(updateProduct).executeUpdate();
                        logger.debug("Product stock was updated.");
                    });
                }

                // Verify is exist products that have prices equalts to $0.00 
                CriteriaQuery<ServiceOrderHasProduct> queryFindProducts = builder.createQuery(ServiceOrderHasProduct.class);
                Root<ServiceOrderHasProduct> rootFindProducts = queryFindProducts.from(ServiceOrderHasProduct.class);

                Predicate equalsToServiceOrder = builder.equal(rootFindProducts.get("serviceOrder"), new ServiceOrder(orderId));
                Predicate priceEqualsToZero = builder.equal(rootFindProducts.get("price"), 0f);

                queryFindProducts.where(builder.and(equalsToServiceOrder, priceEqualsToZero));

                List<ServiceOrderHasProduct> productsByServiceOrder = session.createQuery(queryFindProducts).getResultList();

                Float subTotals = 0f;

                if (!productsByServiceOrder.isEmpty()) {

                    if (location.getId().equalsIgnoreCase("4d0859be-e31a-4acb-a690-2a5c2cb027a7")) {
                        for (ServiceOrderHasProduct sohp : productsByServiceOrder) {
                            ServiceOrderHasProduct serviceOrderHasProduct = session.find(ServiceOrderHasProduct.class, sohp.getId());

                            serviceOrderHasProduct.setPrice(sohp.getProduct().getAmount().floatValue());
                            serviceOrderHasProduct.setTotal(serviceOrderHasProduct.getPrice() * serviceOrderHasProduct.getQuantity());
                            serviceOrderHasProduct.setMayoreo(sohp.getProduct().getUtility().floatValue());
                            serviceOrderHasProduct.setLastUpdatedBy(user);
                            serviceOrderHasProduct.setLastUpdatedOn(DateWrapper.getTodayMXTime());

                            session.update(serviceOrderHasProduct);

                            subTotals += serviceOrderHasProduct.getTotal();

                            logger.info("Service order detail for product " + serviceOrderHasProduct.getProduct() + " was updated.");
                        }
                    } else {
                        for (ServiceOrderHasProduct sohp : productsByServiceOrder) {
                            ServiceOrderHasProduct serviceOrderHasProduct = session.find(ServiceOrderHasProduct.class, sohp.getId());

                            serviceOrderHasProduct.setPrice(sohp.getProduct().getAmount2().floatValue());
                            serviceOrderHasProduct.setTotal(serviceOrderHasProduct.getPrice() * serviceOrderHasProduct.getQuantity());
                            serviceOrderHasProduct.setMayoreo(sohp.getProduct().getUtility2().floatValue());
                            serviceOrderHasProduct.setLastUpdatedBy(user);
                            serviceOrderHasProduct.setLastUpdatedOn(DateWrapper.getTodayMXTime());

                            session.update(serviceOrderHasProduct);

                            subTotals += serviceOrderHasProduct.getTotal();

                            logger.info("Service order detail for product " + serviceOrderHasProduct.getProduct() + " was updated.");
                        }
                    }

                }

                CriteriaUpdate<ServiceOrder> updateOrder = builder.createCriteriaUpdate(ServiceOrder.class);
                Root<ServiceOrder> rootOrder = updateOrder.from(ServiceOrder.class);

                if (!productsByServiceOrder.isEmpty()) {
                    ServiceOrder tmpServiceOrder = session.find(ServiceOrder.class, orderId);

                    Float ivaSO = tmpServiceOrder.getIva() + (subTotals * .16f);
                    Float subtotalSO = tmpServiceOrder.getSubtotal() + subTotals;

                    updateOrder.set(rootOrder.get("subtotal"), subtotalSO);
                    updateOrder.set(rootOrder.get("iva"), ivaSO);
                    updateOrder.set(rootOrder.get("total"), subtotalSO + ivaSO);
                }

                updateOrder.set(rootOrder.get("serviceOrderType"), ServiceOrderType.APPROVED);
                updateOrder.set(rootOrder.get("approvateDate"), DateWrapper.getTodayMXTime());
                updateOrder.set(rootOrder.get("lastUpdatedBy"), user);
                updateOrder.set(rootOrder.get("lastUpdatedOn"), DateWrapper.getTodayMXTime());

                updateOrder.where(builder.equal(rootOrder.get("id"), orderId));

                total = session.createQuery(updateOrder).executeUpdate();

                if (1 == total) {
                    logger.debug("Service order was updated.");

                    transaction.commit();
                    logger.debug("order " + orderId + " was authorized.");

                    success = true;
                } else {
                    logger.error("Service Orders cannot be authorized, reason: service order not updated");
                    rollback(transaction);
                }
            } else {
                logger.error("Service Orders cannot be authorized, reason: Taxi details not updated");
                rollback(transaction);
            }
        } catch (HibernateException e) {
            logger.error("Service Orders cannot be authorized ", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method authorizeServiceOrder() ", e);
            rollback(transaction);
        }
        return success;
    }

    /**
     * Delte all products that a order has and after delete the service order
     * Id<br>Note: Just a service order that has PENDING status can be delete.
     *
     * @param orderId Service order id.
     * @return True if products and service order were deteled otherwise false.
     */
    private boolean deleteServiceOrder(String orderId) {
        logger.debug("deleteServiceOrder");

        boolean success = false;
        Transaction transaction = null;

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

            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaDelete<ServiceOrderHasProduct> delete = builder.createCriteriaDelete(ServiceOrderHasProduct.class);
            Root<ServiceOrderHasProduct> root = delete.from(ServiceOrderHasProduct.class);

            delete.where(builder.equal(root.get("serviceOrder"), new ServiceOrder(orderId)));

            int total = session.createQuery(delete).executeUpdate();
            logger.debug("Total of products deleted " + total);

            CriteriaDelete<ServiceOrder> deleteOrder = builder.createCriteriaDelete(ServiceOrder.class);
            Root<ServiceOrder> rootOrder = deleteOrder.from(ServiceOrder.class);

            deleteOrder.where(builder.equal(rootOrder.get("id"), orderId));

            total = session.createQuery(deleteOrder).executeUpdate();

            if (1 == total) {
                transaction.commit();
                success = true;
                logger.debug("Service order " + orderId + " was deleted");
            } else {
                logger.error("Total " + total + ", Service order " + orderId + " can't be deleted.");
                transaction.rollback();
            }
        } catch (HibernateException e) {
            logger.error("Service Orders cannot be deleted ", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method deleteServiceOrder() ", e);
            rollback(transaction);
        }
        return success;
    }

    /**
     *
     * @param orderId
     * @param status
     * @param userId
     * @return
     */
    private boolean updateStatus(String orderId, ServiceOrderType status, String userId) {
        logger.debug("updateStatus");

        boolean success = false;
        String statusPath = "pendingDate";
        Transaction transaction = null;

        try {
            switch (status) {
                case APPROVED:
                    statusPath = "approvateDate";
                    break;
                case PROGRESS:
                    statusPath = "progressDate";
                    break;
                case FINISHED:
                    statusPath = "finishDate";
                    break;

            }

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

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

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

            update.set(root.get(statusPath), DateWrapper.getTodayMXTime());
            update.set(root.get("serviceOrderType"), status);
            update.set(root.get("lastUpdatedBy"), new User(userId, ""));
            update.set(root.get("lastUpdatedOn"), DateWrapper.getTodayMXTime());

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

            session.createQuery(update).executeUpdate();

            transaction.commit();
            logger.debug("order " + orderId + " was updated.");

            success = true;
        } catch (HibernateException e) {
            logger.error("Service Orders cannot be authorized ", e);
            rollback(transaction);
        } catch (Exception e) {
            logger.error("Method authorizeServiceOrder() ", e);
            rollback(transaction);
        }
        return success;
    }

    @Override
    public List<ServiceOrder> findRangeOfDateFromOrderByDriverIdBetweenStartAndEndDate(String driverId, Date startDate, Date endDate) {
        List<ServiceOrder> results = new ArrayList<>();
        Transaction transaction = null;

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

            String findTaxiByDriver = "SELECT id as id FROM Taxi WHERE driver = :driver AND active = true ORDER BY createdOn DESC";
            String taxiId = session.createQuery(findTaxiByDriver, String.class).setParameter("driver", new Person(driverId)).setMaxResults(1).getSingleResult();

            StringBuilder builder = new StringBuilder();

            builder.append("SELECT name AS name, pending_date AS pending_date, ");
            builder.append("CASE ");
            builder.append("WHEN service_order_type IN ('PENDING','APPROVED','PROGRESS') ");
            builder.append("THEN totalDaysInARangeOfDates(pending_date, CURDATE(), false) ");
            builder.append("WHEN service_order_type = 'FINISHED' ");
            builder.append("THEN totalDaysInARangeOfDates(pending_date, finish_date, false) ");
            builder.append("END AS total_of_days ");
            builder.append("FROM SERVICE_ORDER ");
            builder.append("WHERE service_order_type IN ('PENDING','APPROVED','PROGRESS','FINISHED') AND ");
            builder.append("CASE ");
            builder.append("WHEN service_order_type IN ('PENDING','APPROVED','PROGRESS') AND DATE(pending_date) <= DATE(:endDate) ");
            builder.append("THEN 1 = 1 ");
            builder.append("WHEN DATE(finish_date) BETWEEN DATE(:startDate) AND DATE(:endDate) ");
            builder.append("THEN 1 = 1 ");
            builder.append("ELSE 1 = 0 ");
            builder.append("END ");
            builder.append("AND taxi_id = :taxiId");

            String strQuery = builder.toString();

            Query query = session.createNativeQuery(strQuery, Tuple.class);

            query.setParameter("startDate", startDate);
            query.setParameter("endDate", endDate);
            query.setParameter("taxiId", taxiId);

            List<Tuple> tuples = query.getResultList();

            tuples.stream().map(tuple -> {
                Calendar calendar = Calendar.getInstance();
                ServiceOrder serviceOrder = new ServiceOrder();
                calendar.setTime(tuple.get("pending_date", Date.class));
                serviceOrder.setName(tuple.get("name", String.class));
                serviceOrder.setPendingDate(calendar.getTime());
                if (1 < tuple.get("total_of_days", Integer.class)) {
                    calendar.add(Calendar.DAY_OF_MONTH, tuple.get("total_of_days", Integer.class));
                }
                serviceOrder.setFinishDate(calendar.getTime());
                return serviceOrder;
            }).forEachOrdered(serviceOrder -> {
                results.add(serviceOrder);
            });
            transaction.commit();
        } catch (Exception e) {
            logger.error("Method findRangeOfDateFromOrderByTaxiBetweenStartAndEndDate() ", e);
            if (null != transaction) {
                transaction.rollback();
            }
        }
        return results;
    }

    private static final long serialVersionUID = -5442217801740543194L;
    private final Logger logger = LogManager.getLogger(getClass());

}
