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.User;
import com.arrebol.taxiservicios.model.enums.ServiceOrderType;
import com.arrebol.taxiservicios.model.enums.StopNotificationType;
import com.arrebol.taxiservicios.model.stocktaking.ServiceOrderNotification;
import com.arrebol.taxiservicios.model.stocktaking.ServiceOrder;
import com.arrebol.taxiservicios.model.stocktaking.ServiceOrderHasProduct;
import com.arrebol.taxiservicios.model.stocktaking.ServiceOrderNotificationDeliveryTo;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
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;

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

    /**
     *
     * @param operation 1 = Update service order<br/>2 = Approve service
     * order<br/>3 = Delete service order<br/>4 = Change status to
     * PROGRESS<br/>5 = Change status to FINISH
     * @param orderId
     * @param orderHasProducts
     * @param so
     * @param userId
     * @param taxiId
     * @param km
     * @return
     */
    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);
                        }
                    case 7:
                        if (ServiceOrderType.FINISHED.equals(serviceOrder.getServiceOrderType())) {
                            success = updateStatus(orderId, ServiceOrderType.PAYED, userId);
                        }    
                        break;
                }
            }
        } catch (HibernateException e) {
            rollback(transaction);
            logger.error("Cannot workWithServiceOrder " + e);
        } catch (Exception e) {
            rollback(transaction);
            logger.error("Method workWithServiceOrderList", e);
        }
        return success;
    }

    /**
     *
     * @param notification
     * @return
     */
    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);
        }
    }

    /**
     *
     * @param userId
     * @param permissionLst
     * @param stopNotificationType
     * @return
     */
    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;
    }

    /**
     *
     * @param deliveryToLst
     */
    public synchronized void sendNotifications(List<ServiceOrderNotificationDeliveryTo> deliveryToLst) {
        logger.debug("sendNotifications");

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

            deliveryToLst.forEach((delivery) -> {
                session.save(delivery);
            });

            transaction.commit();
            logger.debug("Notifications sent");
        } catch (HibernateException e) {
            rollback(transaction);
            logger.error("Cannot send Notifications" + e);
        } catch (Exception e) {
            rollback(transaction);
            logger.error("Method sendNotifications", e);
        }
    }

    public static ServiceOrderSingletonController getInstance() {
        if (null == serviceOrderSingletonController) {
            serviceOrderSingletonController = new ServiceOrderSingletonController();
        }
        return serviceOrderSingletonController;
    }

    /**
     *
     * @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("aprobadoUser"), 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";
        String userPath = "pendienteUser";
        Transaction transaction = null;

        try {
            switch (status) {
                case APPROVED:
                    statusPath = "approvateDate";
                    userPath = "aprobadoUser";
                    break;
                case PROGRESS:
                    statusPath = "progressDate";
                    userPath = "progresoUser";
                    break;
                case FINISHED:
                    statusPath = "finishDate";
                    userPath = "terminadoUser";
                    break;
                case PAYED:
                    statusPath = "payDate";
                    userPath = "payUser";
                    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(userPath), new User(userId, ""));
            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;
    }

    private static final long serialVersionUID = -5442217801740543193L;
    private static ServiceOrderSingletonController serviceOrderSingletonController;
    private final Logger logger = LogManager.getLogger(ServiceOrderSingletonController.class);

    public ServiceOrderSingletonController() {
    }

}
