/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License") you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.ofbiz.product.product.test

import org.apache.ofbiz.base.util.UtilDateTime
import org.apache.ofbiz.entity.GenericValue
import org.apache.ofbiz.order.shoppingcart.CheckOutHelper
import org.apache.ofbiz.order.shoppingcart.ShoppingCart
import org.apache.ofbiz.order.shoppingcart.ShoppingCartItem
import org.apache.ofbiz.service.testtools.OFBizTestCase
import org.apache.ofbiz.order.shoppingcart.product.ProductPromoWorker.ActionResultInfo
import org.apache.ofbiz.service.ServiceUtil

class ProductPromoActionTests extends OFBizTestCase {

    ProductPromoActionTests(String name) {
        super(name)
    }

    /**
     * This test check if the function productTaxPercent work correctly
     *  1. test failed with passing non valid value
     *  2. test success if the tax percent promo is set for tax percent
     */
    void testActionProductTaxPercent() {
        ShoppingCart cart = loadOrder('DEMO10090')

        Map<String, Object> serviceContext = prepareConditionMap(cart, 10, true)
        Map<String, Object> serviceResult = dispatcher.runSync('productPromoActTaxPercent', serviceContext)

        //Check result service false test
        assert ServiceUtil.isSuccess(serviceResult)
        assert !serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount == 0

        //Increase item quantity to generate higher tax amount
        for (ShoppingCartItem item : cart.items()) {
            if (!item.getIsPromo()) {
                item.setQuantity(300, dispatcher, cart)
            }
        }

        //Add tax to cart
        CheckOutHelper coh = new CheckOutHelper(dispatcher, delegator, cart)
        coh.calcAndAddTax(false)

        serviceResult = dispatcher.runSync('productPromoActTaxPercent', serviceContext)

        //Check result service
        assert ServiceUtil.isSuccess(serviceResult)
        assert serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount != null
    }

    /**
     * This test check if the function productShipCharge work correctly
     *  1. test failed with passing non valid value
     *  2. test success if the ship charge promo is set for the shipping amount
     */
    void testProductShipCharge() {
        ShoppingCart cart = loadOrder('DEMO10090')

        Map<String, Object> serviceContext = prepareConditionMap(cart, 10, true)
        Map<String, Object> serviceResult = dispatcher.runSync('productPromoActShipCharge', serviceContext)

        //Check result service false test
        assert ServiceUtil.isSuccess(serviceResult)
        assert !serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount == 0

        //Add shipgroup estimate to cart
        cart.setItemShipGroupEstimate(22 , 0)

        serviceResult = dispatcher.runSync('productPromoActShipCharge', serviceContext)

        //Check result service
        assert ServiceUtil.isSuccess(serviceResult)
        assert serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount != null
    }

    /**
     * This test check if the function PoductSpecialPrice work correctly
     *  1. test failed with passing non valid value
     *  2. test success if the special price is set
     */
    void testPoductSpecialPrice() {
        ShoppingCart cart = loadOrder('DEMO10091')

        Map<String, Object> serviceContext = prepareConditionMap(cart, 10, false)
        GenericValue productPromoAction = from('ProductPromoAction')
                .where('productPromoId', '9013', 'productPromoRuleId', '01', 'productPromoActionSeqId', '01').queryOne()
        serviceContext.productPromoAction = productPromoAction
        Map<String, Object> serviceResult = dispatcher.runSync('productPromoActProdSpecialPrice', serviceContext)

        //Check result service false test
        assert ServiceUtil.isSuccess(serviceResult)
        assert !serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount == 0

        //Add item to cart to trigger action
        int itemIndex = cart.addItemToEnd('WG-1111', 100, 10, null, null, null, null, null, dispatcher, null, null)
        ShoppingCartItem item = cart.findCartItem(itemIndex)
        if (item) {
            item.setSpecialPromoPrice(BigDecimal.valueOf(22))
        }

        serviceResult = dispatcher.runSync('productPromoActProdSpecialPrice', serviceContext)

        //Check result service
        assert ServiceUtil.isSuccess(serviceResult)
        assert serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount != null
    }

    /**
     * This test check if the function ProductOrderAmount work correctly
     *  1. test success if the order amount off is set
     */
    void testProductOrderAmount() {
        ShoppingCart cart = loadOrder('DEMO10090')

        Map<String, Object> serviceContext = prepareConditionMap(cart, 10, false)
        GenericValue productPromoAction = from('ProductPromoAction')
                .where('productPromoId', '9012', 'productPromoRuleId', '01', 'productPromoActionSeqId', '01').queryOne()
        serviceContext.productPromoAction = productPromoAction
        Map<String, Object> serviceResult = dispatcher.runSync('productPromoActOrderAmount', serviceContext)

        //Check result service
        assert ServiceUtil.isSuccess(serviceResult)
        assert serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount != null

        // no condition so no false test
    }

    /**
     * This test check if the function productOrderPercent work correctly
     *  1. test success if the order percent off promo is set
     *  2. test failed with passing non valid value
     */
    void testProductOrderPercent() {
        ShoppingCart cart = loadOrder('DEMO10090')

        Map<String, Object> serviceContext = prepareConditionMap(cart, 10, false)
        GenericValue productPromoAction = from('ProductPromoAction')
                .where('productPromoId', '9019', 'productPromoRuleId', '01', 'productPromoActionSeqId', '01').queryOne()
        serviceContext.productPromoAction = productPromoAction
        Map<String, Object> serviceResult = dispatcher.runSync('productPromoActOrderPercent', serviceContext)

        //Check result service
        assert ServiceUtil.isSuccess(serviceResult)
        assert serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount != null

        //Update cart to cancel trigger action
        cart.clearAllAdjustments()
        for (ShoppingCartItem item : cart.items()) {
            if (!item.getIsPromo()) {
                GenericValue product = from('Product').where('productId', item.getProductId()).queryOne()
                if (product != null) {
                    product.put('includeInPromotions', 'N')
                    item.product = product
                }
            }
        }

        serviceContext.shoppingCart = cart
        serviceContext.actionResultInfo = new ActionResultInfo()
        serviceResult = dispatcher.runSync('productPromoActOrderPercent', serviceContext)

        //Check result service false test
        assert ServiceUtil.isSuccess(serviceResult)
        assert !serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount == 0
    }

    /**
     * This test check if the function productPromoActProdPrice work correctly
     *  1. test failed with passing non valid value
     *  2. test success if promo is applied
     */
    void testProductPrice() {
        ShoppingCart cart = loadOrder('DEMO10090')

        Map<String, Object> serviceContext = prepareConditionMap(cart, 10, false)
        GenericValue productPromoAction = from('ProductPromoAction')
                .where('productPromoId', '9015', 'productPromoRuleId', '01', 'productPromoActionSeqId', '01').queryOne()
        serviceContext.productPromoAction = productPromoAction
        Map<String, Object> serviceResult = dispatcher.runSync('productPromoActProdPrice', serviceContext)

        //Check result service false test
        assert ServiceUtil.isSuccess(serviceResult)
        assert !serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount == 0

        //Update cart to trigger action
        for (ShoppingCartItem item : cart.items()) {
            if (!item.getIsPromo()) {
                item.setQuantity(20, dispatcher, cart)
            }
        }

        serviceContext.shoppingCart = cart
        serviceContext.actionResultInfo = new ActionResultInfo()
        serviceResult = dispatcher.runSync('productPromoActProdPrice', serviceContext)

        //Check result service
        assert ServiceUtil.isSuccess(serviceResult)
        assert serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount != null
    }

    /**
     * This test check if the function productPromoActProdAMDISC work correctly
     *  1. test success if promo is applied
     *  2. test failed with passing already applied promo
     */
    void testProductAMDISC() {
        ShoppingCart cart = loadOrder('DEMO10090')

        Map<String, Object> serviceContext = prepareConditionMap(cart, 10, false)
        GenericValue productPromoAction = from('ProductPromoAction')
                .where('productPromoId', '9015', 'productPromoRuleId', '01', 'productPromoActionSeqId', '01').queryOne()
        serviceContext.productPromoAction = productPromoAction
        Map<String, Object> serviceResult = dispatcher.runSync('productPromoActProdAMDISC', serviceContext)

        //Check result service
        assert ServiceUtil.isSuccess(serviceResult)
        assert serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount != null

        serviceContext.shoppingCart = cart
        serviceContext.actionResultInfo = new ActionResultInfo()
        serviceResult = dispatcher.runSync('productPromoActProdAMDISC', serviceContext)

        //Check result service false test
        assert ServiceUtil.isSuccess(serviceResult)
        assert !serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount == 0
    }

    /**
     * This test check if the function productPromoActProdDISC work correctly
     *  1. test success if promo is applied
     *  2. test failed with passing already applied promo
     */
    void testProductDISC() {
        ShoppingCart cart = loadOrder('DEMO10090')

        Map<String, Object> serviceContext = prepareConditionMap(cart, 10, false)
        GenericValue productPromoAction = from('ProductPromoAction')
                .where('productPromoId', '9015', 'productPromoRuleId', '01', 'productPromoActionSeqId', '01').queryOne()
        serviceContext.productPromoAction = productPromoAction
        Map<String, Object> serviceResult = dispatcher.runSync('productPromoActProdDISC', serviceContext)

        //Check result service
        assert ServiceUtil.isSuccess(serviceResult)
        assert serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount != null

        //Check result service false test
        serviceContext.shoppingCart = cart
        serviceContext.actionResultInfo = new ActionResultInfo()
        serviceResult = dispatcher.runSync('productPromoActProdDISC', serviceContext)

        assert ServiceUtil.isSuccess(serviceResult)
        assert !serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount == 0
    }

    /**
     * This test check if the function ProductGWP work correctly
     *  1. test failed with passing non valid value
     *  2. test success if gift with purchase promo is set for order
     */
    void testProductGWP() {
        ShoppingCart cart = loadOrder('DEMO10090')

        Map<String, Object> serviceContext = prepareConditionMap(cart, 10, true)
        Map<String, Object> serviceResult = dispatcher.runSync('productPromoActGiftGWP', serviceContext)

        //Check result service false test
        assert ServiceUtil.isSuccess(serviceResult)
        assert !serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount == 0

        serviceContext.shoppingCart = cart
        serviceContext.actionResultInfo = new ActionResultInfo()
        GenericValue productPromoAction = from('ProductPromoAction')
                .where('productPromoId', '9017',  'productPromoRuleId', '01', 'productPromoActionSeqId', '01').queryOne()
        serviceContext.productPromoAction = productPromoAction
        serviceResult = dispatcher.runSync('productPromoActGiftGWP', serviceContext)

        //Check result service
        assert ServiceUtil.isSuccess(serviceResult)
        assert serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount != null
    }

    /**
     * This test check if the function productActFreeShip work correctly
     *  1. test success if the free shipping promo is set for tax percent
     *  2. don't need to make false test because this fonction doesn't need condition
     */
    void testFreeShippingAct() {
        ShoppingCart cart = loadOrder('DEMO10090')

        Map<String, Object> serviceContext = prepareConditionMap(cart, 10, true)
        Map<String, Object> serviceResult = dispatcher.runSync('productPromoActFreeShip', serviceContext)

        //Check result service
        assert ServiceUtil.isSuccess(serviceResult)
        assert serviceResult.actionResultInfo.ranAction
        assert serviceResult.actionResultInfo.totalDiscountAmount != null
    }

    private ShoppingCart loadOrder(String orderId) {
        Map<String, Object> serviceCtx = [orderId: orderId,
                                          skipInventoryChecks: true, // the items are already reserved, no need to check again
                                          skipProductChecks: true, // the products are already in the order, no need to check their validity now
                                          userLogin: getUserLogin('system')]
        Map<String, Object> loadCartResp = dispatcher.runSync('loadCartFromOrder', serviceCtx)

        return loadCartResp.shoppingCart
    }

    private Map prepareConditionMap(ShoppingCart cart, BigDecimal amount, boolean persist) {
        GenericValue productPromoAction = delegator.makeValue('ProductPromoAction', [amount: amount, orderAdjustmentTypeId: 'PROMOTION_ADJUSTMENT'])
        if (persist) {
            GenericValue productPromo = delegator.makeValue('ProductPromo', [productPromoId: 'TEST'])
            delegator.createOrStore(productPromo)
            GenericValue productPromoRule = delegator.makeValue('ProductPromoRule', [productPromoId: 'TEST', productPromoRuleId: '01'])
            delegator.createOrStore(productPromoRule)
            productPromoAction.productPromoId = 'TEST'
            productPromoAction.productPromoRuleId = '01'
            productPromoAction.productPromoActionSeqId = '01'
            delegator.createOrStore(productPromoAction)
        }
        return [shoppingCart: cart, nowTimestamp: UtilDateTime.nowTimestamp(), actionResultInfo: new ActionResultInfo(),
                productPromoAction: productPromoAction]
    }

}
