package com.qcadoo.mes.technologies.tree;

import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import com.google.common.collect.Lists;
import com.qcadoo.mes.basic.constants.ProductFields;
import com.qcadoo.mes.basic.constants.UnitConversionItemFieldsB;
import com.qcadoo.mes.technologies.TechnologyService;
import com.qcadoo.mes.technologies.constants.*;
import com.qcadoo.model.api.DataDefinition;
import com.qcadoo.model.api.DataDefinitionService;
import com.qcadoo.model.api.Entity;
import com.qcadoo.model.api.search.SearchRestrictions;
import com.qcadoo.model.api.units.PossibleUnitConversions;
import com.qcadoo.model.api.units.UnitConversionService;
import com.qcadoo.view.api.ComponentState;
import com.qcadoo.view.api.ViewDefinitionState;

@Service
public class RemoveTOCService {

    @Autowired
    private TechnologyService technologyService;

    @Autowired
    private DataDefinitionService dataDefinitionService;

    @Autowired
    private UnitConversionService unitConversionService;

    @Transactional
    public boolean removeOnlySelectedOperation(final Entity tocToDelete, final ViewDefinitionState view) {
        List<Entity> usageInProductStructureTree = dataDefinitionService
                .get(TechnologiesConstants.PLUGIN_IDENTIFIER, TechnologiesConstants.MODEL_PRODUCT_STRUCTURE_TREE_NODE).find()
                .add(SearchRestrictions.belongsTo(ProductStructureTreeNodeFields.OPERATION, tocToDelete)).list().getEntities();
        if (!usageInProductStructureTree.isEmpty()) {
            view.addMessage(
                    "technologies.technologyDetails.window.treeTab.technologyTree.error.cannotDeleteOperationUsedInProductStructureTree",
                    ComponentState.MessageType.FAILURE, false,
                    usageInProductStructureTree.stream()
                            .map(e -> e.getBelongsToField(ProductStructureTreeNodeFields.MAIN_TECHNOLOGY)
                                    .getStringField(TechnologyFields.NUMBER))
                            .distinct().collect(Collectors.joining(", ")));
            return false;
        }

        final Entity parent = tocToDelete.getBelongsToField(TechnologyOperationComponentFields.PARENT);
        final List<Entity> operationsToRewrite = tocToDelete.getHasManyField(TechnologyOperationComponentFields.CHILDREN);

        if (parent == null) {
            if (operationsToRewrite.size() > 1) {
                view.addMessage("technologies.technologyDetails.window.treeTab.technologyTree.error.cannotDeleteRoot",
                        ComponentState.MessageType.FAILURE);
                return false;
            } else if (operationsToRewrite.size() == 1) {
                return createNewRoot(operationsToRewrite.get(0), tocToDelete, view);
            } else {
                deleteOldToc(tocToDelete);
                return true;
            }
        }
        final Optional<Entity> mainOutProductComponentToDelete = technologyService.tryGetMainOutputProductComponent(tocToDelete);
        final List<Entity> originalInProducts = tocToDelete
                .getHasManyField(TechnologyOperationComponentFields.OPERATION_PRODUCT_IN_COMPONENTS);
        List<Entity> newInProducts = Lists.newArrayList();

        for (Entity toc : operationsToRewrite) {
            Optional<Entity> mainOutProductComponent = technologyService.tryGetMainOutputProductComponent(toc);
            if (mainOutProductComponent.isPresent()) {
                Optional<Entity> maybeInProductComponent = getInProductComponentFromProductComponent(originalInProducts,
                        mainOutProductComponent.get());

                if (maybeInProductComponent.isPresent()) {
                    Entity inProductComponent = maybeInProductComponent.get();
                    newInProducts.add(inProductComponent);
                }
            }
            setNewParent(toc, parent);
        }
        if (!rewriteInProductComponents(parent, newInProducts, mainOutProductComponentToDelete, view)) {

            view.addMessage("technologies.technologyDetails.window.treeTab.technologyTree.error.cannotRewriteProducts",
                    ComponentState.MessageType.FAILURE);
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return false;
        }
        deleteOldToc(tocToDelete);

        return true;
    }

    private boolean rewriteInProductComponents(Entity toc, List<Entity> productComponentsToAdd, Optional<Entity> productToDelete,
            ViewDefinitionState view) {
        List<Entity> originalInProducts = Lists
                .newArrayList(toc.getHasManyField(TechnologyOperationComponentFields.OPERATION_PRODUCT_IN_COMPONENTS));
        List<String> addedProducts = Lists.newArrayList();
        if (productToDelete.isPresent()) {
            Optional<Entity> originalProductToDelete = getInProductComponentFromProductComponent(originalInProducts,
                    productToDelete.get());
            originalProductToDelete.ifPresent(originalInProducts::remove);
        }

        for (Entity productComponentToAdd : productComponentsToAdd) {
            Optional<Entity> maybeOriginalInProduct = getInProductComponentFromProductComponent(originalInProducts,
                    productComponentToAdd);
            if (maybeOriginalInProduct.isPresent()) {
                Entity originalInProduct = maybeOriginalInProduct.get();
                sumQuantities(originalInProduct, productComponentToAdd);
            } else {
                setNewOperationComponent(productComponentToAdd, toc);
                originalInProducts.add(productComponentToAdd);
                addedProducts.add(productComponentToAdd.getBelongsToField(OperationProductInComponentFields.PRODUCT)
                        .getStringField(ProductFields.NUMBER));
            }
        }

        toc.setField(TechnologyOperationComponentFields.OPERATION_PRODUCT_IN_COMPONENTS, originalInProducts);

        Entity savedToc = toc.getDataDefinition().save(toc);
        if (savedToc.isValid() && !addedProducts.isEmpty()) {
            view.addMessage("technologies.technologyDetails.window.treeTab.technologyTree.success.productsRewrote",
                    ComponentState.MessageType.INFO, String.join(", ", addedProducts),
                    toc.getBelongsToField(TechnologyOperationComponentFields.OPERATION).getStringField(OperationFields.NUMBER));
        }
        return savedToc.isValid();
    }

    private void sumQuantities(Entity originalInProduct, Entity productComponentToAdd) {

        BigDecimal originalQuantity = originalInProduct.getDecimalField(OperationProductInComponentFields.QUANTITY);
        BigDecimal quantityToAdd = productComponentToAdd.getDecimalField(OperationProductInComponentFields.QUANTITY);
        String givenUnit = originalInProduct.getStringField(OperationProductInComponentFields.GIVEN_UNIT);
        Entity product = originalInProduct.getBelongsToField(OperationProductInComponentFields.PRODUCT);
        String baseUnit = product.getStringField(ProductFields.UNIT);

        BigDecimal quantity = originalQuantity.add(quantityToAdd);
        originalInProduct.setField(OperationProductInComponentFields.QUANTITY, quantity);

        if (baseUnit.equals(givenUnit)) {
            originalInProduct.setField(OperationProductInComponentFields.GIVEN_QUANTITY, quantity);
        } else {
            PossibleUnitConversions unitConversions = unitConversionService.getPossibleConversions(baseUnit,
                    searchCriteriaBuilder -> searchCriteriaBuilder
                            .add(SearchRestrictions.belongsTo(UnitConversionItemFieldsB.PRODUCT, product)));
            if (unitConversions.isDefinedFor(givenUnit)) {
                BigDecimal convertedQuantity = unitConversions.convertTo(quantity, givenUnit);
                originalInProduct.setField(OperationProductInComponentFields.GIVEN_QUANTITY, convertedQuantity);
            } else {
                originalInProduct.addError(
                        originalInProduct.getDataDefinition().getField(OperationProductInComponentFields.GIVEN_QUANTITY),
                        "technologies.operationProductInComponent.validate.error.missingUnitConversion");
                originalInProduct.setField(OperationProductInComponentFields.GIVEN_QUANTITY, null);
            }
        }
    }

    private boolean createNewRoot(Entity newRoot, final Entity tocToDelete, final ViewDefinitionState view) {

        if (setNewParent(newRoot, null)) {
            deleteOldToc(tocToDelete);
        } else {
            view.addMessage("technologies.technologyDetails.window.treeTab.technologyTree.error.cannotCreateNewRoot",
                    ComponentState.MessageType.FAILURE);
            return false;
        }
        return true;
    }

    private void deleteOldToc(final Entity tocToDelete) {
        getTocDataDefinition().delete(tocToDelete.getId());
    }

    private DataDefinition getTocDataDefinition() {
        return dataDefinitionService.get(TechnologiesConstants.PLUGIN_IDENTIFIER,
                TechnologiesConstants.MODEL_TECHNOLOGY_OPERATION_COMPONENT);
    }

    private boolean setNewParent(Entity toc, final Entity newParent) {
        toc.setField(TechnologyOperationComponentFields.PARENT, newParent);
        Entity saved = getTocDataDefinition().save(toc);
        return saved.isValid();
    }

    private void setNewOperationComponent(Entity inProduct, final Entity newToc) {
        inProduct.setField(OperationProductInComponentFields.OPERATION_COMPONENT, newToc);
        inProduct.getDataDefinition().save(inProduct);
    }

    private Optional<Entity> getInProductComponentFromProductComponent(final List<Entity> originalInProducts,
            final Entity productComponent) {
        Long productId = productComponent.getBelongsToField(OperationProductOutComponentFields.PRODUCT).getId();
        return originalInProducts.stream().filter(inProduct -> inProduct
                .getBelongsToField(OperationProductInComponentFields.PRODUCT).getId().compareTo(productId) == 0).findAny();
    }
}
