<?php

namespace Hilco\Models;

use Auth;
use DB;
use Debugbar;
use Exception;
use HilcoB2B\M3Request;
use Hilco\GuzzleWrappers\APIGuzzleWrapper;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Log;

class WebCart extends WebModel
{
    protected $table = 'WebCarts';
    protected $fillable = ['quantity', 'comment'];

    public function webPart() {
        return $this->hasOne(WebPart::class, 'part_number', 'part_number');
    }

    public function webUser() {
        return $this->hasOne(WebUser::class, 'webuser_id', 'id');
    }

    public function priceList() {
//        return $this->hasOne(PriceList::class)
    }

    public function scopeForUser($query, $webuserId) {
        return $query->where('webuser_id', $webuserId);
    }

    public function scopePartNumber($query, $partNumber) {
        return $query->where('part_number', $partNumber);
    }

    public function getExtendedPriceAttribute() {
        return $this->webPart->getCustomerPrice($this->quantity) * $this->quantity;
    }

    /**
     * @return bool TRUE if cart item is linked to a valid, web-visible webpart to the end-user, FALSE otherwise
     */
    public function isAvailable() {
        return isset($this->webPart) && $this->webPart->visibleInSilo(b2b()->activeWebSilo());
    }

    public static function add(WebPart $webPart, $quantity = 1)
    {
        $instance = new static;
        $instance->webuser_id = Auth::id();
        $instance->part_number = $webPart->part_number;
        $instance->quantity = $quantity;
        $instance->save();
        return $instance;
    }

    public static function remove($userId, $partNumber)
    {
        $instance = new static;
        return $instance::where(['part_number' => $partNumber, 'webuser_id' => $userId])->delete();
    }

    /**
     * @param $webUser
     * @param $withTranslations
     * @return array|WebCart[]|\Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection
     */
    public static function getWebCartItems($webUser = null, $withTranslations = true) {
        if (is_null($webUser)) $webUser = auth()->user();
        return
            self
            ::where('webuser_id', ($webUser->id))
            ->orderBy('part_number', 'asc')
            ->with(['webPart' => function ($query) use ($withTranslations) {
                if ($withTranslations) {
                    $query = $query->with('translations');
                }
                return $query->with(['part' => function ($query) {
                    return $query->with('inventoryItems')->with('productFamily.productCategory.productCategoryGroup');
                }]);
            }])
            ->get()
        ;
    }

    /* Calculates and keeps track of the overall status of the active user's cart, including all discounts and pricing */
    public static function cartStatus(WebSilo $webSilo = null, WebUser $webUser = null, Customer $soldToCustomer = null, $useDummy = true) {
        if (is_null($webSilo)) $webSilo = b2b()->activeWebSilo();
        if (is_null($webUser)) $webUser = auth()->user();
        if (is_null($soldToCustomer)) $soldToCustomer = b2b()->activeCustomer();

        if (is_null($webSilo) || is_null($webUser) || is_null($soldToCustomer)) return [];

        // Initialize 'status' object
        $status = [
            'itemsCount' => 0,
            'quantityCount' => 0,
            'baseTotal' => 0,
            'discountedTotal' => 0,
            'discounts' => [
                'rewards' => 0,
                'customer' => 0,
            ],
            'productCategoryTotals' => [],
            'rewardsUpgradeSpending' => 0,
            'rewardsTier' => rewards()->tier($soldToCustomer),
            'taxAmount' => 0,
            'shippingAmount' => 0,
            'shippingDiscountAmount' => 0,
            'shippingTaxAmount' => 0,
            'requireApproval' => false,
            'priceList' => '',
            'missingPrices' => false,
            'noStock' => false,
            'hasMedicalPriority' => false,
        ];

        $items = [];

        $allCartItemsAreAvailable = true;

        // get all current items in the web cart - [array of WebCart objects]
        $webCartItems = self::getWebCartItems($webUser);
        if (is_countable($webCartItems)) {
            $numCartLines = count($webCartItems);
        } else {
            $numCartLines = 0;
        }


        // match part ids to relevant info (base price, total qty, price list, is_contract)
        // From this we get two lists; one with the individual lines, one with the altogether parts.
        if($numCartLines > 0) {
            $distincts = self::getDistinctPartInfo($webCartItems, $soldToCustomer, $webUser, $useDummy);
            $distinctPartInfo = $distincts['distinctPartInfo'];
            $distinctLineInfo = $distincts['distinctLineInfo'];

            $atpData = [];
            foreach ($webCartItems as $webCartItem) {
                $atpData[] = $webCartItem->part_number;
            }

            $atps = [];
            try {
                $atps = WebPart::fetchAllItemsStockInfo($atpData, null, $soldToCustomer);
                foreach($atps as $atp){
                    if(($atp['status'] == Part::LOW_STOCK && $atp['stock'] == 0) || $atp['status'] == Part::UNKNOWN){
                        $status['noStock'] = true;
                        break;
                    }
                }
            }catch(Exception $e){
                Log::error("Exception caught while fetching stock info: " . $e->getMessage());
            }
        }else{
            $distinctPartInfo = [];
            $distinctLineInfo = [];
            $atps = [];
        }


        // get all "almost qualifying" promotions (used to conditionally show cart and checkout headers)
        $almostQualifyingPromotionsInfo = self::getAlmostQualifiedPromotionsInfo($soldToCustomer, $webCartItems, $distinctPartInfo);
        $status['almostQualifiedPromotionsInfo'] = $almostQualifyingPromotionsInfo;

        $status['customerTriggeredPromotions'] = self::getCustomerTriggeredPromotions($soldToCustomer, $webCartItems, $distinctPartInfo);

        // get relevant discounts and other info from all triggered promotions
        $triggeredPromotionsInfo = self::getTriggeredPromotionsInfo($soldToCustomer, $webCartItems, $distinctPartInfo);

        // set individual promo info objects and lists for use later
        $orderHeaderDiscounts = $triggeredPromotionsInfo['orderHeaderDiscounts'];
        $triggeredPromotions = $triggeredPromotionsInfo['triggeredPromotions'];
        $discountNotes = $triggeredPromotionsInfo['discountNotes'];
        $freeItemActionsInfo = $triggeredPromotionsInfo['freeItemActionsInfo'];

        // get the current cheapest item in the cart that qualifies for a free-item promotion
//        $cheapestItemAndPromo = self::getCheapestWebCartItemsByProduct($freeItemActionsInfo, $webCartItems, $customerPrices, $siloPrices, $distinctPartQuantities, $priceListNameToUse, $soldToCustomer);
//        $cheapestItem = $cheapestItemAndPromo['cheapestItem'];
//        $freeItemPromoUsed = $cheapestItemAndPromo['promotionUsed'];
//        $hasAppliedFreeItem = false;

        foreach ($webCartItems as $key=>$webCartItem) {
            // Let's catch any cart items that have become invalid/unavailable and mark them as such
            if (!$webCartItem->isAvailable()) {
                $webPart = $webCartItem->webPart()->withTrashed()->first();
                if (isset($webPart)) {
                    $part = $webPart->part()->withTrashed()->first();
                } else {
                    $part = null;
                }
                $items[$webCartItem->id] = collect([
                    'id' => $webCartItem->id,
                    'webCart' => $webCartItem,
                    'webPart' => $webPart,
                    'part' => $part,
                    'quantity' => $webCartItem->quantity,
                    'available' => false,
                ]);

                $allCartItemsAreAvailable = false;
                continue;
            }
            $part = $webCartItem->webPart->part;
            $partInfo = [];
            if (!empty($distinctLineInfo)) {
                foreach ($distinctLineInfo as $lineInfo) {
                    if ($lineInfo['partId'] == $part->id && $lineInfo['totalQuantity'] == $webCartItem->quantity) {
                        $partInfo = $lineInfo;
                        break;
                    }
                }
            }

            $appliedPromos = [];

            $basePrice = empty($partInfo['basePrice']) ? 0 : $partInfo['basePrice'];
            if($basePrice == trans('messages.temporarily_unavailable')){
                $status['missingPrices'] = true;
            }

            $isContractPrice = 0;
            $priceListUsed = null;
            if (!empty($partInfo['basePrice'])) {
                if (!empty($partInfo['isContractPrice']) && $partInfo['isContractPrice']) {
                    $isContractPrice = 1;
                }
                if (!empty($partInfo['priceListUsed'])) {
                    $priceListUsed = $partInfo['priceListUsed'];
                }
            }

            $itemDiscounts = !empty($partInfo['discounts']) ? $partInfo['discounts'] : [];

            $discountPercent = 0;
            $totalDiscAmount = 0;
            $baseDiscountAmount = 0;
            $extendedDiscountAmount = 0;
            $m3AppliedDiscountType = null;
            $m3AppliedDiscountNdx = 1;
            $discounts = [];
            if(!empty($partInfo['basePrice']) && $basePrice != trans('messages.temporarily_unavailable')) {
                $discountedPrice = $basePrice;
                $extendedBasePrice = $basePrice * $webCartItem->quantity;

                // As long as this is non-contract pricing, calculate all the applicable discounts for the part
                if (!$isContractPrice) {
                    // if there are still free item actions to process..
                    if (is_countable($freeItemActionsInfo) && count($freeItemActionsInfo)) {
                        // loop thru each action's list of cheapest parts...
                        foreach ($freeItemActionsInfo as $key => $value) {
                            $freeItemAction = $value['freeItemAction'];
                            $cheapestPartList = $value['cheapestPartList'];
                            // if they still have parts to discount...
                            if (is_countable($cheapestPartList) && count($cheapestPartList)) {
                                foreach ($cheapestPartList as $partId => $numFree) {
                                    // if this part is still on the list to discount...
                                    if ($numFree > 0) {
                                        // and the current item matches...
                                        if ($part->id == $partId) {
                                            // if the number of times this item should be set to free is less than the current
                                            // qty on this cart line, then use that number, otherwise, just use the cart line qty
                                            $numToDiscount = ($numFree < $webCartItem->quantity) ? $numFree : $webCartItem->quantity;
                                            // calculate the actual discount percentage for the line to set that number of items as 'free'
                                            $freePercent = round($numToDiscount * 100 * (1 / $webCartItem->quantity), 2);
                                            // make sure the cart line's discount percentage is capped at 100%
                                            if ($discountPercent + $freePercent >= 100) {
                                                $freePercent = 100 - $discountPercent;
                                            }
                                            // if there is still a discount to be applied..
                                            if ($freePercent !== 0) {
                                                // keep track of current discount percentage
                                                $discountPercent += $freePercent;
                                                // increment the percent, amt, and extAmt values for the promo disc type
                                                $discountAmount = round($extendedBasePrice * ($freePercent / 100), 2);
                                                $totalDiscAmount += $discountAmount;

                                                $appliedPromos[] = [
                                                    'promoActionType' => 'freeItemAction',
                                                    'promoActionId' => $freeItemAction->id,
                                                    'percent' => $freePercent,
                                                    'amount' => $discountAmount
                                                ];

                                                if($totalDiscAmount >= $extendedBasePrice){
                                                    foreach ($itemDiscounts as $discType => $discount) {
                                                        if($discType == 'promotion') {
                                                            array_increment($discounts, "promotion.percent", $freePercent);
                                                            array_increment($discounts, "promotion.baseAmount", round($discountAmount / $webCartItem->quantity, 2));
                                                            array_increment($discounts, "promotion.extendedAmount", $discountAmount);
                                                            array_increment($discounts, "promotion.isUsed", 1);

                                                            foreach ($discount as $discAttr => $attrValue) {
                                                                if (Str::contains($discAttr, 'TX8')) {
                                                                    $discounts['promotion']['m3Type'] = trim($attrValue, ' ');
                                                                    $discounts['promotion']['m3Ndx'] = $discAttr[3];
                                                                }
                                                            }
                                                        }
                                                    }
                                                }

                                                // make sure the promo that had this free item action is updated to 'applied'
                                                $triggeredPromotions = self::updateAppliedPromotions($triggeredPromotions, $freeItemAction->promotion()->first());
                                                // decrement the number on the cheapest part list to keep track...
                                                $cheapestPartList[$partId] = $numFree - $numToDiscount;
                                                // ...and remove the item from the list once they've all been discounted
                                                if ($cheapestPartList[$partId] == 0) {
                                                    unset($cheapestPartList[$partId]);
                                                }
                                                $freeItemActionsInfo[$key] = [
                                                    'freeItemAction' => $freeItemAction,
                                                    'cheapestPartList' => $cheapestPartList
                                                ];
                                            } else {
                                                // otherwise stop calculating for this free item actions part list, it's already at 100%
                                                break;
                                            }
                                        }
                                    } else {
                                        unset($cheapestPartList[$partId]);
                                        $freeItemActionsInfo[$key] = [
                                            'freeItemAction' => $freeItemAction,
                                            'cheapestPartList' => $cheapestPartList
                                        ];
                                    }
                                }
                            } else {
                                unset($freeItemActionsInfo[$key]);
                            }
                        }
                    }

                    if ($totalDiscAmount < $extendedBasePrice) {
                        $totalDiscAmount = 0;
                        $discountPercent = 0;

                        $hasCustomerDiscounts = false;
                        $hasRewardsDiscounts = false;
                        $hasPromotionalDiscounts = false;
                        $hasM3Discounts = false;
                        $hasRewardsExclusion = false;

                        $customerDiscPercent = 0;
                        $customerDiscAmnt = 0;
                        $rewardsDiscPercent = 0;
                        $rewardsDiscAmnt = 0;
                        $promotionalDiscPercent = 0;
                        $promotionalDiscAmnt = 0;
                        $m3DiscPercent = 0;
                        $m3DiscAmnt = 0;

                        foreach ($itemDiscounts as $discType => $discount) {
                            $dPercent = null;
                            $dAmount = null;
                            $dType = null;
                            $m3Type = null;
                            $m3Ndx = 1;
                            foreach($discount as $discAttr => $attrValue){
                                if (Str::contains($discAttr, 'DIP')) {
                                    $dPercent = floatval(trim($attrValue, ' '));
                                } else if (Str::contains($discAttr, 'DIA')) {
                                    $dAmount = floatval(trim($attrValue, ' '));
                                } else if (Str::contains($discAttr, 'TX8')) {
                                    $dType = $discType;
                                    $m3Type = trim($attrValue, ' ');
                                    $m3Ndx = $discAttr[3];
                                }
                            }
                            // keep track of customer and rewards discounts (see if-check after for-loop)
                            if ($dType === 'customer' && ($dPercent > 0 || $dAmount > 0)) {
                                $customerDiscPercent = $dPercent;
                                $customerDiscAmnt = $dAmount;
                                $hasCustomerDiscounts = true;
                            } else if ($dType === 'rewards' && ($dPercent > 0 || $dAmount > 0)) {
                                $rewardsDiscPercent = $dPercent;
                                $rewardsDiscAmnt = $dAmount;
                                $hasRewardsDiscounts = true;
                            } else if ($dType === 'promotion'){
                                $appliedPromos = array_merge($appliedPromos, $part->getApplicableDiscounts($soldToCustomer, $triggeredPromotions, $partInfo));
                                if(!empty($appliedPromos)){
                                    foreach($appliedPromos as $appliedPromo){
                                        $dPercent += $appliedPromo['percent'];
                                        $dAmount += $appliedPromo['amount'];
                                    }

                                    $hasPromotionalDiscounts = true;
                                }else{
                                    $dPercent = 0;
                                    $dAmount = 0;
                                }
                                $promotionalDiscPercent = $dPercent;
                                $promotionalDiscAmnt = $dAmount;
                            }else{
                                $m3DiscPercent += $dPercent;
                                $m3DiscAmnt += $dAmount;
                                $hasM3Discounts = true;
                            }

                            if(!\Illuminate\Support\Arr::has($discounts, $dType)){
                                $discounts[$dType] = [];
                            }

                            array_increment($discounts[$dType], "percent", $dPercent);
                            array_increment($discounts[$dType], "baseAmount", round($dAmount / $webCartItem->quantity, 2));
                            array_increment($discounts[$dType], "extendedAmount", $dAmount);
                            array_increment($discounts[$dType], "isUsed", 1);
                            $discounts[$dType]['m3Type'] = $m3Type;
                            $discounts[$dType]['m3Ndx'] = $m3Ndx;
                        }

                        // if the item has both customer and rewards discounts, only add the greater of the two, not both
                        if ($hasCustomerDiscounts && $hasRewardsDiscounts) {
                            if ($customerDiscPercent > $rewardsDiscPercent) {
                                $discountPercent += $customerDiscPercent;
                                $totalDiscAmount += $customerDiscAmnt;
                                $discounts['rewards']['isUsed'] = 0;
                                $m3AppliedDiscountType = $discounts['customer']['m3Type'] ;
                                $m3AppliedDiscountNdx =$discounts['customer']['m3Ndx'] ;
                            } else {
                                $discountPercent += $rewardsDiscPercent;
                                $totalDiscAmount += $rewardsDiscAmnt;
                                $discounts['customer']['isUsed'] = 0;
                                $m3AppliedDiscountType = $discounts['rewards']['m3Type'] ;
                                $m3AppliedDiscountNdx =$discounts['rewards']['m3Ndx'] ;
                            }
                        } else if ($hasCustomerDiscounts) {
                            $discountPercent += $customerDiscPercent;
                            $totalDiscAmount += $customerDiscAmnt;
                            $m3AppliedDiscountType = $discounts['customer']['m3Type'] ;
                            $m3AppliedDiscountNdx =$discounts['customer']['m3Ndx'] ;
                        } else if ($hasRewardsDiscounts) {
                            $discountPercent += $rewardsDiscPercent;
                            $totalDiscAmount += $rewardsDiscAmnt;
                            $m3AppliedDiscountType = $discounts['rewards']['m3Type'] ;
                            $m3AppliedDiscountNdx =$discounts['rewards']['m3Ndx'] ;
                        }

                        if ($hasPromotionalDiscounts){
                            $discountPercent += $promotionalDiscPercent;
                            $totalDiscAmount += $promotionalDiscAmnt;
                        }

                        if($hasM3Discounts){
                            $discountPercent += $m3DiscPercent;
                            $totalDiscAmount += $m3DiscAmnt;
                        }
                    }else{
                        foreach ($itemDiscounts as $discType => $discount) {
                            if ($discType != 'promotion') {
                                if(!\Illuminate\Support\Arr::has($discounts, $discType)){
                                    $discounts[$discType] = [];
                                }

                                array_increment($discounts[$discType], "percent", 0);
                                array_increment($discounts[$discType], "baseAmount", 0);
                                array_increment($discounts[$discType], "extendedAmount", 0);
                                array_increment($discounts[$discType], "isUsed", 0);

                                foreach ($discount as $discAttr => $attrValue) {
                                    if (Str::contains($discAttr, 'TX8')) {
                                        $discounts[$discType]['m3Type'] = trim($attrValue, ' ');
                                        $discounts[$discType]['m3Ndx'] = $discAttr[3];
                                    }
                                }
                            }
                        }
                    }


                    if ($totalDiscAmount >= $extendedBasePrice){
                        $totalDiscAmount = $extendedBasePrice;
                        $discountPercent = 100;
                    }

                    $baseDiscountAmount = round($totalDiscAmount / $webCartItem->quantity, 2);
                    $extendedDiscountAmount = $totalDiscAmount;
                    $discountedPrice = round($basePrice - $baseDiscountAmount, 2);
                }
            }else{
                $discountedPrice = 0;
                $extendedBasePrice = 0;
                $baseDiscountAmount = 0;
                $discountPercent = 0;
                $extendedDiscountAmount = 0;
            }

            $extendedPrice = $extendedBasePrice - $extendedDiscountAmount;

            // If applicable, calculate the tax amount
            $taxInfo = [];
            try {
                $taxInfo = self::calculateTaxAmount($webCartItem->webPart, $extendedPrice);
            }catch(Exception $e){
//                Log::error("Exception caught while fetching tax info: " . $e->getMessage());
                $taxInfo['isTaxable'] = 0;
                $taxInfo['taxAmount'] = 0;
            }

            // If any part requires approval to be ordered, set the status of the cart to require approval
            $itemRequiresApproval = $webCartItem->webPart->requireApproval;
            if ($itemRequiresApproval) {
                $status['requireApproval'] = true;
            }

            if ($webCartItem->webPart->part->is_medical_priority) {
                $status['hasMedicalPriority'] = true;
            }

            // Add the part and its relevant details to the $items list
            $items[$webCartItem->id] = collect([
                'id' => $webCartItem->id,
                'webCart' => $webCartItem,
                'webPart' => $webCartItem->webPart,
                'atp' => \Illuminate\Support\Arr::get($atps, $webCartItem->part_number, ['status' => Part::UNKNOWN, 'stock' => 0]),
                'part' => $webCartItem->webPart->part,
                'quantity' => $webCartItem->quantity,
                'basePrice' => $basePrice,
                'discountedPrice' => $discountedPrice,
                'baseDiscountAmount' => $baseDiscountAmount,
                'discountPercent' => $discountPercent,
                'extendedBasePrice' => $extendedBasePrice,
                'extendedPrice' => $extendedPrice,
                'extendedDiscountAmount' => $extendedDiscountAmount,
                'discounts' => $discounts,
                'appliedPromos' => $appliedPromos,
                'm3DiscountUsed' => $m3AppliedDiscountType,
                'm3DiscountNdx' => $m3AppliedDiscountNdx,
                'isContractPrice' => $isContractPrice,
                'priceListUsed' => $priceListUsed,
                'isTaxable' => $taxInfo['isTaxable'],
                'taxAmount' => $taxInfo['taxAmount'],
                'requireApproval' => $itemRequiresApproval,
                'available' => true,
            ]);
            array_increment($status, 'itemsCount');
            array_increment($status, 'quantityCount', $webCartItem->quantity);
            array_increment($status, 'baseTotal', $extendedBasePrice);
            array_increment($status, 'taxAmount', $taxInfo['taxAmount']);

            foreach ($discounts as $discountType => $discountInfo) {
                if (isset($discountInfo['isUsed']) && $discountInfo['isUsed']) {
                    if(in_array($discountType, ['customer', 'rewards', 'promotion'])) {
                        array_increment($status, "discounts.$discountType", $discountInfo['extendedAmount']);
                    }else{
                        array_increment($status, "discounts.hilco", $discountInfo['extendedAmount']);
                    }
                }
            }

            $productCategoryGroup = $part->productFamily->productCategory->productCategoryGroup;
            if(!is_null($productCategoryGroup)){
                array_increment($status, "productCategoryTotals.$productCategoryGroup->id", $extendedPrice);
            }
        }

        // set the ones that won't change and are necessary for other uses in $status
        $status['triggeredPromotions'] = $triggeredPromotions;
        $status['discountNotes'] = $discountNotes;
        $status['orderHeaderDiscounts'] = $orderHeaderDiscounts;

        // Check to see if this order will qualify the active user's customer account for a Rewards Program tier upgrade
        if (rewards()->isRegistered() && b2b()->isBillingDirect() && rewards()->isEligible($soldToCustomer, false)) {
            $rewardsUpgradeSpending = rewards()->calculateUpgradeSpending($status, $soldToCustomer);
            $status['rewardsUpgradeSpending'] = $rewardsUpgradeSpending;

            // If the order will cause a tier upgrade, recalculate the 'discounts.rewards' discount type in $status
            if ($status['rewardsUpgradeSpending'] < 0) { //Tier Upgrade
                $status['baseRewardsTier'] = $status['rewardsTier'];
                $tierJump = $status['rewardsUpgradeSpending'] * -1;
                $status['rewardsTier'] = min(4, rewards()->tier() + $tierJump);
                $status['upgradedTier'] = min(4, rewards()->tier() + $tierJump);

                $rewardsDiscountTotal = 0;
                foreach ($items as $itemKey => $item) {
                    if (\Illuminate\Support\Arr::has($item, 'discounts.rewards')) {
                        $discountPercent = rewards()->discountForPartTier($item['part']->id, $status['upgradedTier'], $soldToCustomer);
                        $discountAmount = round($item['basePrice'] * ($discountPercent / 100), 2);
                        $extendedDiscountAmount = $discountAmount * $item['quantity'];
                        $baseDiscountAmount = round($item['basePrice'] * ($discountPercent / 100), 2);
                        $discountedPrice = $item['basePrice'] - $baseDiscountAmount;
                        $extendedPrice = $discountedPrice * $item['quantity'];
                        $rewardsDiscountTotal += $extendedDiscountAmount;
                        //I don't think this is right...this shouldn't be applied until we know for sure whether the reward discounts should be applied

                        /**
                         *  The old method didn't actually add any objects that would be considered in the checkout controller.
                         * It might never have actually worked.
                         * This new method will actually inject the new discount values (replacing them because these are
                         * discounts acquainted with the new upgrade tier.
                         */
                        $discounts = $item->get('discounts');
                        $discounts['rewards']['percent'] = $discountPercent;
                        $discounts['rewards']['baseAmount'] = $discountAmount;
                        $discounts['rewards']['extendedAmount'] = $extendedDiscountAmount;
                        $item->put('discounts', $discounts);

                        // pretty sure these fields are vestigial and unused now, leaving until we clean up web cart someday
                        $item->put('baseDiscountAmount', $discountAmount);
                        $item->put('extendedDiscountAmount', $extendedDiscountAmount);
                        $item->put('discountedPrice', $discountedPrice);
                        $item->put('extendedPrice', $extendedPrice);

                        // m3DiscountUsed is crucial here because it's how CheckoutController::placeOrder() determines
                        // if it should be creating a SalesOrderDiscLine row for the rewards discount on a line item
                        $item->put('m3DiscountUsed', $discounts['rewards']['m3Type']);
                        $item->put('m3DiscountNdx', $discounts['rewards']['m3Ndx']);
                    }
                }
                $status['discounts']['rewards'] = $rewardsDiscountTotal;
            }
        }

        // Calculate any "header-level" discounts applied via promotion(s)
        $calculatedOrderHeaderDiscountMax = 0;
        foreach ($orderHeaderDiscounts as $orderHeadDiscount) {
            $discountMax = \Illuminate\Support\Arr::get($orderHeadDiscount, 'maximum_dollar_amount');
            $calculatedOrderHeaderDiscountMax = max($calculatedOrderHeaderDiscountMax, $discountMax);
        }

        $calculatedOrderHeaderDiscountAmount = 0;
        foreach ($orderHeaderDiscounts as $headerDiscount) {
            $discountPercent = \Illuminate\Support\Arr::get($headerDiscount, 'discount_percent');
            $discountMaxAmount = \Illuminate\Support\Arr::get($headerDiscount, 'maximum_dollar_amount');
            $discountAmount = round($status['baseTotal'] * ($discountPercent/100), 2);
            if ($discountMaxAmount > 0) {
                $discountAmount = min($discountMaxAmount, $discountAmount);
            }
            $calculatedOrderHeaderDiscountAmount += $discountAmount;
        }

        if ($calculatedOrderHeaderDiscountMax > 0) {
            $calculatedOrderHeaderDiscountAmount = min($calculatedOrderHeaderDiscountAmount, $calculatedOrderHeaderDiscountMax);
        }

        array_increment($status, "discounts.promotion", $calculatedOrderHeaderDiscountAmount);

        // Assign 'discountedTotal' to be the combined values of all discount types in $status
        $status['discountedTotal'] = \Illuminate\Support\Arr::get($status, 'baseTotal');
        foreach (\Illuminate\Support\Arr::get($status, 'discounts') as $type => $amt) {
            $status['discountedTotal'] = \Illuminate\Support\Arr::get($status, 'discountedTotal') - $amt;
        }
        if($status['discountedTotal'] < 0 ){
            $status['discountedTotal'] = 0;
        }

        // Rate Shop the order
        $rate = b2b()->activeRate();
        if ($rate && !$rate->isError()) {
            $status['shippingAmount'] = $rate->rate();
            $status['shippingDiscountAmount'] = $rate->discountAmount();
            $status['shippingTaxAmount'] = $rate->taxAmount();
        }

        // Get the actual order total (discounted total plus any taxes)
        $status['orderTotal'] = $status['discountedTotal'] + $status['taxAmount'];

        if ((b2b()->activeSpendingLimit() > 0 && $status['discountedTotal'] > b2b()->activeSpendingLimit()) ||
            (b2b()->activeMinimumOrder() > 0 && $status['discountedTotal'] < b2b()->activeMinimumOrder())) {

            $status['requireApproval'] = true;
        }

        if (self::getQuantityNeedsApproval($webCartItems)) {
            $status['requireApproval'] = true;
        }

        $status['allCartItemsAreAvailable'] = $allCartItemsAreAvailable;
        $status['items'] = $items;

        return $status;
    }

    /**
     * Get the list of all triggered promotions and their relevant info (discounts, etc)
     * @param $customer
     * @param $webCartItems (These could also be SubscriptionOrderLines, as far as I can tell this isn't actually used anywhere)
     * @param $distinctPartInfo (This is what we actually use)
     * @return array
     * [
     *     'triggeredPromotions' => [array of Promotion objects, with extra 'applied' field],
     *     'discountNotes'=> [array of Discount Notes - DEPRECATED],
     *     'orderHeaderDiscounts' => [array of the following arrays, for each Order Discount action: ['discount_percent' => ?, 'maximum_dollar_amount' => ?]],
     *     'freeItemActions' => [array of PromotionFreeItemAction objects]
     * ]
     */
    public static function getTriggeredPromotionsInfo($customer, $webCartItems, $distinctPartInfo) {
        if ($customer == null) {
            $customer = b2b()->activeCustomer();
        }
        $custDivision = $customer->getDivisionByFacility();
        if (!$customer->billsDirect || is_null($custDivision) || $custDivision->division_trimmed == Division::DE_CODE) {
            return [
                'triggeredPromotions' => [],
                'discountNotes'=> [],
                'orderHeaderDiscounts' => [],
                'freeItemActionsInfo' => []
            ];
        }

        $promotions = Promotion::getActivePromotions();
        $orderHeaderDiscounts = [];
        $triggeredPromotions = [];
        $discountNotes = [];
        $freeItemActionsInfo = [];

        foreach($promotions as $promotion) {
            $triggeredAllTriggers = true;
            $triggers = $promotion->triggers;
            foreach($triggers as $trigger) {
                $triggeredAllTriggers = $triggeredAllTriggers && $trigger->details->isTriggered($customer, $webCartItems, $distinctPartInfo);
            }

            if($triggeredAllTriggers) {
                foreach ($promotion->orderDiscountActions as $discountAction) {
                    $orderHeaderDiscounts[] = [
                        'discount_percent' => \Illuminate\Support\Arr::get($discountAction, 'discount_percent'),
                        'maximum_dollar_amount' => \Illuminate\Support\Arr::get($discountAction, 'maximum_dollar_amount'),
                        'action_id' => $discountAction->id
                    ];
                }

                foreach ($promotion->freeItemActions as $freeItemAction) {
                    $freeItemActionsInfo[] = [
                        'freeItemAction' => $freeItemAction,
                        'cheapestPartList' => self::getCheapestWebCartItemsForAFreeItemPromo($freeItemAction, $distinctPartInfo)
                    ];
                }

                if (count(\Illuminate\Support\Arr::get($promotion, 'orderDiscountActions', [])) ||
                    count(\Illuminate\Support\Arr::get($promotion, 'shippingDiscountActions', [])) ||
                    count(\Illuminate\Support\Arr::get($promotion, 'orderCommentActions', []))) {
                    $promotion['applied'] = true;
                }

                $triggeredPromotions[] = $promotion;
            }
        }

        // if multiple price list actions are set across multiple promotions
//        if (count($priceListActions) !== 1) $priceListActions = [];

        return
        [
            'triggeredPromotions' => $triggeredPromotions,
            'discountNotes'=> $discountNotes,
            'orderHeaderDiscounts' => $orderHeaderDiscounts,
//            'priceListActions' => $priceListActions,
            'freeItemActionsInfo' => $freeItemActionsInfo
        ];
    }

    /**
     * @param $customer
     * @param $webCartItems (These could also be SubscriptionOrderLines, as far as I can tell this isn't actually used anywhere)
     * @param $distinctPartInfo (This is what we actually use)
     * This gathers promotions that might fit the customer.
     */
    public static function getCustomerTriggeredPromotions($customer, $webCartItems, $distinctPartInfo){
        if ($customer == null) {
            $customer = b2b()->activeCustomer();
        }
        $custDivision = $customer->getDivisionByFacility();
        if (!$customer->billsDirect || is_null($custDivision) || $custDivision->division_trimmed == Division::DE_CODE) {
            return [];
        }

        $customerTriggeredPromotions = [];
        $promotions = Promotion::getActivePromotions();
        foreach($promotions as $promotion){
            $triggers = $promotion->triggers;
            foreach($triggers as $trigger){
                if($trigger->trigger_type != "customerTrigger"){
                    continue;
                }
                $details = $trigger->details;
                if(is_null($details) || !$details->isTriggered($customer, $webCartItems, $distinctPartInfo)){ //Technically, if this happens, something is wrong with the trigger instantiation.
                    continue 2;
                }
            }
            $customerTriggeredPromotions[] = $promotion;
        }

        return $customerTriggeredPromotions;
    }

    /**
     * @param $customer
     * @param $webCartItems (These could also be SubscriptionOrderLines, as far as I can tell this isn't actually used anywhere)
     * @param $distinctPartInfo (This is what we actually use)
     * @return array
     */
    public static function getAlmostQualifiedPromotionsInfo($customer, $webCartItems, $distinctPartInfo) {
        if ($customer == null) {
            $customer = b2b()->activeCustomer();
        }
        $custDivision = $customer->getDivisionByFacility();
        if (!$customer->billsDirect || is_null($custDivision) || $custDivision->division_trimmed == Division::DE_CODE) {
            return [];
        }
        /**
         *  Addendum [9-14-2018]:
         *  Promotions with Almost Qualified Triggers should only be considered if all of their other non-AQ triggers
         *      (if they have any others) have been satisfied/triggered.
         *  For example, if the promotion has a code trigger, then unless that code trigger has been activated, any
         *  AlmostQualified conditions will be disregarded.
         */
//        DB::enableQueryLog();
        $promotions = Promotion::getActivePromotions();
        $almostQualifyingPromotionsInfo = [];
//        TODO: Get rid the indexing, replace with basic appending
        $i = 0;
        foreach($promotions as $promotion) {
            $triggers = $promotion->triggers;
            $almostQualifiedValid = true; // This determines if there is a non-AQ trigger that is not satisfied.

            $j = 0;
            $promo = []; // Stores the Promotion and List of Triggers assosciated.
            $trigList = []; // Stores the Triggers w/ Qualifying Fields.
            foreach($triggers as $trigger) {
                if ($trigger->details->hasAlmostQualifyingField()) {
                    $trigDetails = [];
//                    For every promotion that has at least one trigger for an almost qualifying field, we create an object
//                      that stores the promotion, the triggerdetails, and the current cart status in terms of the relevant amounts.
                    $amountLeft = $trigger->details->isAlmostQualifying($customer, $webCartItems, $distinctPartInfo);
                    if ($amountLeft > 0) {
                        $trigDetails['trigger'] = $trigger; // Stores individual trigger and the amount progress their tied to.
                        if($trigger->details->minimum_quantity > 0 ) {
                            $trigDetails['type'] = 'minimum_quantity';
                        }else if($trigger->details->minimum_dollar > 0){
                            $trigDetails['type'] = 'minimum_dollar';
                        }
                        $trigDetails['amountLeft'] = $amountLeft;
                        $trigList[$j] = $trigDetails;        //Stores all Triggers that are tied to the individual promotion.
                        $j++;
                    }
                }else{
                    if(!$trigger->details->isTriggered($customer, $webCartItems, $distinctPartInfo)){
                        $almostQualifiedValid = false;
                    }
                }
            }

            if(count($trigList) && $almostQualifiedValid){
                $promo['triggers'] = $trigList;
                $promo['promotion'] = $promotion;
                $almostQualifyingPromotionsInfo[$i] = $promo;
                $i++;
            }
        }
//        dd(DB::getQueryLog());
        return $almostQualifyingPromotionsInfo;
    }

    public static function fetchAllItemPrices($pricingData, $includeDiscounts = true, $customer = null, $webuser = null, $useDummy = true){
        $client = null;
        $guzzle = new M3Request();


        if ($customer == null) {
            $customer = b2b()->activeCustomer();
        }

        $webSilo = b2b()->activeWebSilo();
        $facility = $customer->activeSegment->facility_id;

        if ($useDummy) {
            // This is where we need overrides for using the dummy customer (See Email from Kelly - Pricing Flow VW 12-17-20.docx)

            $customersegment = $customer->activeSegment;
            $soldto = $customer;
            $soldtoparent = $customersegment->parentCustomer; // could be null
            $payer = $customersegment->billToCustomer;
            $payerParent = $payer->activeSegment->parentCustomer; // could be null

            $dummyApplies = true;

            // Check to see if these overrides should just be skipped and go straight to never showing dummy
            if ($webSilo->never_override_listprice == 1) {
                $dummyApplies = false;
            } else {
                //Otherwise check the rules for specific circumstance

                //if sold-to = payer
                if ($webSilo->soldto_payer_listprice == 1 && $soldto->cust_no == $payer->cust_no) {
                    $dummyApplies = false;
                }
                // if sold-to parent = payer
                if ($webSilo->soldtoparent_payer_listprice == 1 && $soldtoparent != null && $soldtoparent->cust_no == $payer->cust_no) {
                    $dummyApplies = false;
                }
                // if soldto parent = payer parent
                if ($webSilo->soldtoparent_payerparent_listprice == 1 &&  $soldtoparent != null && $payerParent != null && $soldtoparent->cust_no = $payerParent->cust_no) {
                    $dummyApplies = false;
                }
            }

            if ($dummyApplies) {
                $customer = $customer->getDummyCustomer();
            }
        }

        if ($webuser == null) {
            $webuser = auth()->user();
        }
        
        if(empty($guzzle->client)){
            Log::error('Exception Caught while performing ' . __FUNCTION__ . ': Guzzle Http Client does not exist. Base URI may be missing!');
            return trans('messages.temporarily_unavailable');
        }else{
            $client = $guzzle->client;
        }

        $chunks = array_chunk($pricingData, 50);
        $response = [];

        foreach($chunks as $chunk){
//            Log::debug("Calling getItemPrice on Hilco API: webUserID => " . auth()->user()->id . " customerNumber => " . $customer->cust_no . " requestInfo => " . json_encode($chunk));


            $chunkResponse = json_decode($client->post('v4/' . 'getItemPrice', ['form_params' => ['requestFrom' => 'b2b', 'webuser' => $webuser->id,
                'customer_number' => $customer->cust_no, 'pricingData' => $chunk, 'facility' => $facility],
                'headers' => ['Accept' => 'application/json']])->getBody()->getContents(), true);

//            Log::debug("B2B getItemPrice chunked response : " . json_encode($chunkResponse));
            $response = array_merge($response, $chunkResponse);
        }

        $prices = [];

        foreach($response as $infoId => $partPriceInfo) {
            if (is_countable($partPriceInfo['details']) && count($partPriceInfo['details']) == 1) {
//                Log::error('Exception Caught while performing ' . __FUNCTION__ . ': ' . $partPriceInfo['details']);
                $prices[$infoId]['basePrice'] = trans('messages.temporarily_unavailable');
            } else if (\Illuminate\Support\Arr::has($partPriceInfo['details'], 'MIRecord')) {
                $results = $partPriceInfo['details']['MIRecord'][0]['NameValue'];
                foreach ($results as $result) {
                    if ($result['Name'] == 'SAPR') {
                        $returnMe = trim($result['Value'], ' ');
                        if (empty($returnMe)) {
                            $prices[$infoId]['basePrice'] = trans('messages.temporarily_unavailable');
                        }else {
                            $prices[$infoId]['basePrice'] = trim($result['Value'], ' ');
                        }
                    }

                    if(!empty($prices[$infoId])) {
                        if ($prices[$infoId]['basePrice'] != trans('messages.temporarily_unavailable')) {
                            if ($result['Name'] == 'NTCD') {
                                $prices[$infoId]['isContractPrice'] = trim($result['Value'], ' ') ? true : false;
                            } else if ($result['Name'] == 'PRRO') {
                                $prices[$infoId]['priceListUsed'] = trim($result['Value'], ' ');
                            }
                        }
                    }
                }

                if($prices[$infoId]['basePrice'] != trans('messages.temporarily_unavailable') && $includeDiscounts) {
                    foreach ($partPriceInfo['discounts'] as $discountNum => $discountInfo) {
                        $discType = null;
                        $hasDiscount = false;
                        foreach ($discountInfo as $key => $value) {
                            if (Str::contains($key, 'TX8')) {
                                $m3DiscountCode = M3DiscountCode::where('discount_code', '=', trim($value, ' '))->first();
                                if(!empty($m3DiscountCode)) {
                                    switch ($m3DiscountCode->discount_type) {
                                        case 'Other':
                                            $discType = 'customer';
                                            break;
                                        case 'Rewards':
                                            $discType = 'rewards';
                                            break;
                                        case 'One-Off':
                                            $discType = 'promotion';
                                            break;
                                    }
                                }else{
                                    $discType = trim($value, ' ');
                                }
                            } else if (Str::contains($key, 'DIC')) {
                                if ($value > 0) {
                                    $hasDiscount = true;
                                }
                            }
                        }

                        if ($hasDiscount && ($discType != null || $discType != '')) {
                            if ($discType == 'customer') {
                                $prices[$infoId]['discounts']['customer'] = $discountInfo;
                            } else if($discType == 'rewards'){
                                $quantity = !empty($pricingData[$infoId]['quantity']) ? $pricingData[$infoId]['quantity'] : 1;
                                $price = round($prices[$infoId]['basePrice'] * $quantity, 2);
                                $prices[$infoId]['discounts']['rewards'] = self::populateRewardsDiscInfo($partPriceInfo['part'], $price, $discountInfo, $customer);
                            } else if($discType == 'promotion'){
                                $prices[$infoId]['discounts']['promotion'] = $discountInfo;
                            } else{
                                $prices[$infoId]['discounts'][$discType] = $discountInfo;
                            }
                        }
                    }
                }
            } else {
//                Log::error('Exception Caught while performing ' . __FUNCTION__ . ': ' . $partPriceInfo['details']['Message']);
                $prices[$infoId]['basePrice'] = trans('messages.temporarily_unavailable');
            }

            if(!empty($pricingData[$infoId]['quantity'])){
                $prices[$infoId]['totalQuantity'] = $pricingData[$infoId]['quantity'];
            }else{
                foreach($pricingData as $priceData){
                    if(!empty($priceData['quantity'])){
                        if($priceData['partNumber'] == $partPriceInfo['part']){
                            $prices[$infoId]['totalQuantity'] = $priceData['quantity'];
                        }
                    }
                }
            }

            if (!empty($pricingData[$infoId]['partId'])) {
                $prices[$infoId]['partId'] = $pricingData[$infoId]['partId'];
            }else if(!empty($pricingData[$infoId]['familyId'])){
                $prices[$infoId]['familyId'] = $pricingData[$infoId]['familyId'];
            }else if(!empty($partPriceInfo['part'])){
                $prices[$infoId]['partNum'] = $partPriceInfo['part'];
            }
        }

        return $prices;
    }

    private static function populateRewardsDiscInfo($partNumber, $regularExtPrice, $discInfo, $customer = null){

        if ($customer == null) {
            $customer = b2b()->activeCustomer();
        }
        $discPercentage = 0;

        $part = Part::wherePartNo($partNumber)->whereNull('deleted_at')->first();
        if(!is_null($part)){
            if(!$part->isRewardsExcluded()) {
                $discPercentage = rewards()->discountForPart($part, $customer);
                $discAmount = round($regularExtPrice * ($discPercentage / 100), 2);
                foreach ($discInfo as $key => $value) {
                    if (strpos($key, "DIP") !== false) {
                        $discInfo[$key] = $discPercentage;
                    } else if (strpos($key, "DIA") !== false) {
                        $discInfo[$key] = $discAmount;
                    }
                }
            }else{
                foreach ($discInfo as $key => $value) {
                    if (strpos($key, "DIP") !== false) {
                        $discInfo[$key] = 0;
                    } else if (strpos($key, "DIA") !== false) {
                        $discInfo[$key] = 0;
                    }
                }
            }
        }

        return $discInfo;
    }

    /**
     * @param $customerPriceListName
     * @param $siloPriceListName
     * @param $currencyCode
     * @param $webCartItems
     * @return array
     * [
     *     part_id => [
     *         'baseprice' => base price of the part from the price list used,
     *         'isContractPrice' => boolean value from PriceLists->contract_flag,
     *         'priceListUsed' => name of the price list that was used,
     *         'totalQuantity' => total quantity of this part in the cart
     *     ]
     * ]
     */
    public static function getDistinctPartInfo($webCartItems, $customer = null, $webuser = null, $useDummy = true) {
        if ($customer == null) {
            $customer = b2b()->activeCustomer();
        }

        if ($webuser == null) {
            $webuser = auth()->user();
        }
        
        $distinctPartQuantities = [];
        foreach ($webCartItems as $webCartItem) {
            if ($webCartItem->isAvailable()) {
                $distinctPartQuantities[$webCartItem->webPart->part->id]['partNumber'] = $webCartItem->part_number;
                array_increment($distinctPartQuantities[$webCartItem->webPart->part->id],'quantity', $webCartItem->quantity);
            }
        }

        $distinctLineQuantities = [];

        foreach($webCartItems as $webCartItem){
            if ($webCartItem->isAvailable()) {
                array_push($distinctLineQuantities, [
                    'partId' => $webCartItem->webPart->part->id,
                    'partNumber' => $webCartItem->part_number,
                    'quantity' => $webCartItem->quantity,
                ]);
            }
        }

        $distincts = [
            'distinctLineInfo' => [],
            'distinctPartInfo' => []
        ];
        try {
            $distinctPartInfo = self::fetchAllItemPrices($distinctPartQuantities, false, $customer, $webuser, $useDummy);
            $distinctLineInfo = self::fetchAllItemPrices($distinctLineQuantities, true, $customer, $webuser, $useDummy);
            $distincts = [
                'distinctLineInfo' => $distinctLineInfo,
                'distinctPartInfo' => $distinctPartInfo
            ];
        }catch(Exception $e){
            Log::error("Exception caught while fetching prices: " . $e->getMessage());
        }

        return $distincts;
    }

    public static function getQuantityNeedsApproval($webCartItems) {

        $distinctParts = [];
        foreach ($webCartItems as $webCartItem) {
            if (isset($webCartItem->webPart) && isset($webCartItem->webPart->part)) {
                array_increment($distinctParts, $webCartItem->webPart->part->id, $webCartItem->quantity);
            }
        }

        foreach ($distinctParts as $key => $value){
            $siloPart = WebSilo_Part::where('part_id', $key)->where('websilo_id', b2b()->activeWebSilo()->id)->first();
            if($siloPart){
                if(($siloPart->minimum_quantity != 0 && $value < $siloPart->minimum_quantity) || ($siloPart->maximum_quantity != 0 && $value > $siloPart->maximum_quantity)){
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * @param $priceListName
     * @param $currencyCode
     * @param $partIds
     * @return array|PriceList[]|\Illuminate\Database\Eloquent\Collection
     */
    private static function getPartPricesByPriceList($priceListName, $currencyCode, $partIds) {
        return PriceList::where('price_list', '=', $priceListName)
                        ->where('currency', '=', $currencyCode)
                        ->whereIn('part_id', $partIds)
                        ->orderBy('quantity_level', 'DESC')
                        ->get();
    }

//    /* Returns the cheapest item in the web cart that matches the product_type hierarchy */
//    private static function getCheapestWebCartItemsByProduct($freeItemActions, $webCartItems, $customerPrices, $siloPrices, $partTotalQuantities, $priceListName, $soldToCustomer) {
//        $cheapestPrice = -1;
//        $cheapestItem = 0;
//        $promotionUsed = null;
//        foreach ($webCartItems as $webCartItem) {
//            $part = $webCartItem->webPart->part;
//
//            $qualifiesForFreeItem = false;
//            foreach ($freeItemActions as $freeItemAction) {
//                if ($freeItemAction->isApplicable($part)) {
//                    $qualifiesForFreeItem = true;
//                    $promotionUsed = $freeItemAction->promotion()->first();
//                    break;
//                }
//            }
//
//            if ($qualifiesForFreeItem) {
//                $partTotalQuantity = \Illuminate\Support\Arr::get($partTotalQuantities, $part->id, 0);
//                $currencyCode = $soldToCustomer->currency;
//                $siloPriceListName = b2b()->activeWebSilo()->priceList;
//                $partPriceInfo = self::getPartPriceInfo($customerPrices, $siloPrices, $part, $partTotalQuantity, $priceListName, $siloPriceListName, $currencyCode);
//                $basePrice = $partPriceInfo['basePrice'];
//                $cheapestPrice = $cheapestPrice == -1 ? $basePrice : min($cheapestPrice, $basePrice);
//                if ($cheapestPrice === $basePrice) $cheapestItem = $webCartItem;
//            }
//        }
//        return [
//            'cheapestItem' => $cheapestItem,
//            'promotionUsed' => $promotionUsed
//        ];
//    }

    /**
     * Determine the cheapest parts that are applicable to a free item promo action,
     * and how many of them can be marked as 'free' in the current cart.
     * @param $freeItemAction
     * @param $distinctPartInfo
     * @return array
     * [
     *     <part_id> => <number to mark as free>
     * ]
     */
    private static function getCheapestWebCartItemsForAFreeItemPromo($freeItemAction, $distinctPartInfo) {
        $cheapestList = [];
        $cheapestItemsFound = 0;
        $applicableItems = [];

        foreach ($distinctPartInfo as $partInfo) {
            $part = Part::wherePartNo($partInfo['partNum'])->first();
            if ($freeItemAction->isApplicable($part)) {
                $applicableItems[] = [
                    'partId' => $part->id,
                    'partInfo' => $partInfo
                ];
            }
        }

        usort($applicableItems, ['\Hilco\Models\WebCart', 'compareWebCartItemByPrice']);

        $maxFree = $freeItemAction->num_free;
        foreach($applicableItems as $key => $value) {
            $partId = $value['partId'];
            $partInfo = $value['partInfo'];
            if ($cheapestItemsFound < $maxFree) {
                $partQuantity = $partInfo['totalQuantity'];
                $quantityToDiscount = ($cheapestItemsFound + $partQuantity <= $maxFree ? $partQuantity : $maxFree - $cheapestItemsFound);
                array_increment($cheapestList, $partId, $quantityToDiscount);
                $cheapestItemsFound += $quantityToDiscount;
            }
        }

        return $cheapestList;
    }

    /**
     * Comparator function for sorting web cart items by their base price
     * Only used in getCheapestWebCartItemsForAFreeItemPromo() right now
     * @param $item1
     * @param $item2
     * @return int
     */
    static function compareWebCartItemByPrice($item1, $item2) {
        $price1 = \Illuminate\Support\Arr::get($item1, 'partInfo.basePrice');
        $price2 = \Illuminate\Support\Arr::get($item2, 'partInfo.basePrice');

        if ($price1 === $price2)
            return 0;

        return ($price1 < $price2) ? -1 : 1;
    }

    /**
     * Returns general price info for a given part, including its base price, whether it's under
     * contract pricing, and which price list was used. Tries to get the customer's price list first,
     * and uses the default WebSilo price list otherwise.
     * @param $customerPrices
     * @param $siloPrices
     * @param $part
     * @param $partTotalQuantity
     * @param $customerPriceListName
     * @param $siloPriceListName
     * @param $currencyCode
     * @return array
     * [
     *     'basePrice' => base price of the part from the price list used,
     *     'isContractPrice' => boolean value from PriceLists->contract_flag,
     *     'priceListUsed' => name of the price list that was used
     * ]
     */
    private static function getPartPriceInfo($customerPrices, $siloPrices, $part, $partTotalQuantity, $customerPriceListName, $siloPriceListName, $currencyCode) {
//        $customerPriceList = PriceList::filterForPartQuantity($customerPrices, $part->id, $partTotalQuantity);
//        $basePrice = WebCart::getItemPrice($part, $partTotalQuantity);
        $basePrice = null;
        $isContractPrice = true;
        $priceListUsed = false;
//        if ($customerPriceList) {
//            $basePrice = $customerPriceList->price;
//            $basePrice = WebCart::getItemPrice($part, $partTotalQuantity);
//            $isContractPrice = $customerPriceList->contract_flag;
//            $priceListUsed = $customerPriceListName;
//        } else {
//            $siloPriceList = PriceList::filterForPartQuantity($siloPrices, $part->id, $partTotalQuantity);
//            if ($siloPriceList) {
//                $basePrice = $siloPriceList->price;
//                $isContractPrice = false;
//                $priceListUsed = $siloPriceListName;
//            } else {
//                $basePrice = $part->getListPriceForCurrencyCode($currencyCode);
//                $isContractPrice = false;
//                $priceListUsed = 'PART_TABLE_LIST_PRICE';
//            }
//        }
        return [
            'basePrice' => $basePrice,
            'isContractPrice' => $isContractPrice,
            'priceListUsed' => $priceListUsed
        ];
    }

    /**
     * Update the triggered promotions list to mark one as 'applied'
     * @param $triggeredPromotions
     * @param $promoToUpdate
     * @return array
     */
    private static function updateAppliedPromotions($triggeredPromotions, $promoToUpdate) {
        $promos = [];
        foreach ($triggeredPromotions as $promo) {
            if ($promo->id === $promoToUpdate->id) {
                $promo['applied'] = true;
            }
            $promos[] = $promo;
        }
        return $promos;
    }

    /**
     * Calculate the part's tax amount, if applicable, based on the current customer and the calculated extended price
     * @param $part
     * @param $extendedPrice
     * @return float|int
     */
    public static function calculateTaxAmount(WebPart $webPart, $extendedPrice, $customer = null, $shipToAddress = null) {
        $itemTaxAmount = 0;
        $taxInfo = [];
        if (b2b()->isCustomerTaxable()) {
            if (b2b()->activeCountryVATTax()) {
                $vatTax = \Illuminate\Support\Arr::get($webPart->part->inventoryItems()->inPlant(b2b()->activePlant()->plant)->first(), 'VATTax', null);

                if(is_null($vatTax)){
                    $taxInfo['isTaxable'] = 0;
                }else{
                    $taxInfo['isTaxable'] = 1;
                    $itemTaxAmount = $extendedPrice * (\Illuminate\Support\Arr::get($vatTax, 'tax_percentage', 0) / 100);
                }
            }else{
                $partTaxability = $webPart->getPartTaxability($extendedPrice, $shipToAddress);
                if ($partTaxability['taxResult'] == 'TAXABLE') {
                    $taxInfo['isTaxable'] = 1;
                    $itemTaxAmount = $partTaxability['calculatedTax'];
                }else{
                    $taxInfo['isTaxable'] = 0;
                }
            }
        }else{
            $taxInfo['isTaxable'] = 0;
        }

        $taxInfo['taxAmount'] = $itemTaxAmount;
        return $taxInfo;
    }

    /**
     * Get the name of the Price List to use on the order -- exists because Croakies Adventures logic can "override"
     * a customer's default price list (see CroakiesHelper.php for more details)
     * @param $customerPriceListName
     * @param $webCartItems
     * @return false|int|null|string
     */
    public static function getPriceListNameToUse($customerPriceListName, $webCartItems) {
        // check Croakies Adventures Race logic if customer is qualifying...
        $priceListNameToUse = croakies()->getPriceListToUse($webCartItems);

        // DISABLED, DEFUNCT AS OF EARLY 2018
//         ...and check if there's a promotion that applies a price list...
//        $priceLists = $triggeredPromotionsInfo['priceListActions'];
//        if (count($priceLists)) $priceListNameToUse = array_values($priceLists)[0];//reset($priceLists);

        //...otherwise just use sold-to customer's default price list
        if (!$priceListNameToUse) $priceListNameToUse = $customerPriceListName;

        return $priceListNameToUse;
    }
}