<?php /** @noinspection SpellCheckingInspection */

namespace Hilco;

use Debugbar;
use Hilco\CenposHelper\CenposWrapper;
use Hilco\CenposHelper\Exceptions\BillingNotActiveForCurrencyException;
use Hilco\Models\ApplicationTranslation;
use Hilco\Models\ApplicationTranslationKey;
use Hilco\Models\AvailableLanguage;
use Hilco\Models\BannedPartsCustomer;
use Hilco\Models\BannedPartsState;
use Hilco\Models\Carrier;
use Hilco\Models\Customer;
use Hilco\Models\CustomerLastLanguageUsed;
use Hilco\Models\CustomerSegment;
use Hilco\Models\CustomerShippingAddress;
use Hilco\Models\DeliveryMethod;
use Hilco\Models\DeliveryTerm;
use Hilco\Models\Division;
use Hilco\Models\Part;
use Hilco\Models\Plant;
use Hilco\Models\Promotion;
use Hilco\Models\PromotionCodeTrigger;
use Hilco\Models\RateShopGroup;
use Hilco\Models\RewardsProductCategoryGroupDiscount;
use Hilco\Models\SubscriptionGroup;
use Hilco\Models\SubscriptionOrder;
use Hilco\Models\SubscriptionPeriod;
use Hilco\Models\Translations_WebCollection;
use Hilco\Models\Translations_WebGroup;
use Hilco\Models\VATTax;
use Hilco\Models\WebAsset;
use Hilco\Models\WebCart;
use Hilco\Models\WebCategory;
use Hilco\Models\WebCollection;
use Hilco\Models\WebFamily;
use Hilco\Models\WebGroup;
use Hilco\Models\WebPart;
use Hilco\Models\WebRole;
use Hilco\Models\WebSilo;
use Hilco\Models\WebUrl;
use Hilco\Shipments\Rate;
use HilcoB2B\M3Request;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use GuzzleHttp\Client;
use Hilco\GuzzleWrappers\APIGuzzleWrapper;
use Hilco\XMLBuilder\RateShopUtility;
use Cookie;
use NumberFormatter;

class B2BHelper
{
    protected $activeCustomer;
    protected $activeWebSilo;
    protected $activeShippingAddress = null;
    protected $activeBillingCustomer = null;
    protected $activeCarrier = null;
    protected $activeRate = null;
    protected $activeSegment = null;
    protected $activePlants = null;
    protected $activeLocale;
    protected $activeLanguage;
    protected $activePromoCodes;
    protected $rewardsProductCategoryGroupDiscounts;
    protected $activeWebGroupsForLayout = null;
    protected $activeWebTranslations = null;
    protected $activeSpendingLimit = 0;
    protected $activeMinimumOrder = 0;


    protected $activePromoCodesLoaded = false;
    protected $activeCustomerLoaded = false;
    protected $activeBillingCustomerLoaded = false;
    protected $activeShippingAddressLoaded = false;
    protected $activeCarrierLoaded = false;
    protected $activeRateLoaded = false;
    protected $activeLocaleLoaded = false;
    protected $activeWebSiloLoaded = false;
    protected $activeSegmentLoaded = false;
    protected $activeLanguageLoaded = false;
    protected $activePlantsLoaded = false;
    protected $rewardsProductCategoryGroupDiscountsLoaded = false;
    protected $activeSpendingLimitLoaded = false;
    protected $activeMinimumOrderLoaded = false;

    public function __construct()
    {
    }

//  ******************************** LOAD ********************************
    protected function loadActiveCustomer()
    {
        $this->activeCustomer = false;

        $aliasedCustomerId = session()->get('b2b.aliasedCustomerId', false);
        if ($aliasedCustomerId) {
            $this->activeCustomer = Customer::with('segments.address.plant', 'discounts', 'availableShippingAddresses', 'webSilo')->find($aliasedCustomerId);
        } else {
            $user = auth()->user();
            if ($user) $this->activeCustomer = Customer::with('segments.address.plant', 'discounts', 'availableShippingAddresses', 'webSilo')->find($user->customer->id);//$user->customer;
        }
        $this->activeCustomerLoaded = true;
    }

    public function loadActiveSegment() {
        $activeCustomer = $this->activeCustomer();

        $this->activeSegment = \Illuminate\Support\Arr::get($activeCustomer, 'activeSegment');

        $this->activeSegmentLoaded = true;
    }

    protected function loadActiveBillingCustomer()
    {
        $this->activeBillingCustomer = false;

        $activeBillingCustomerId = session()->get('b2b.activeBillingCustomerId', false);
        if ($activeBillingCustomerId) {
            $this->activeBillingCustomer = Customer::with('segments.address.plant', 'discounts', 'availableShippingAddresses')->find($activeBillingCustomerId);
        } else {
            $segment = CustomerSegment::where('customer_id', $this->activeCustomer()->id)->first();
            $this->activeBillingCustomer = Customer::with('segments.address.plant', 'discounts', 'availableShippingAddresses')->find($segment->billto_customer_id);
        }
        $this->activeBillingCustomerLoaded = true;
    }

    protected function loadActiveShippingAddress()
    {
        if ($this->activeCustomer() != null){
            $this->activeShippingAddress = $this->activeCustomer()->default_shipping_address;

            $activeShippingAddressId = session()->get('b2b.activeShippingAddressId', false);
            if ($activeShippingAddressId) {
                foreach ($this->activeCustomer()->availableShippingAddresses as $address) {
                    if ($address->id == $activeShippingAddressId) {
                        $this->activeShippingAddress = $address;
                        break;
                    }
                }
            }
        }
        $this->activeShippingAddressLoaded = true;

    }

    protected function loadActiveCarrier()
    {
        $this->activeCarrier = \Illuminate\Support\Arr::get($this->activeShippingAddress(), 'defaultCarrier', false);

        $activeCarrierId = session()->get('b2b.activeCarrierId', false);
        if ($activeCarrierId) {
            $this->activeCarrier = Carrier::find($activeCarrierId);
        }

        $this->activeCarrierLoaded = true;
    }

    protected function loadActiveRate()
    {
        $this->activeRate = session()->get('b2b.activeRate', false);
        $this->activeRateLoaded = true;
    }

    protected function loadActiveWebSilo()
    {
        $this->activeWebSilo = false;
        $user = auth()->user();

//        $activeWebSiloId = !is_null($user) ? session()->get('b2b.activeWebSiloId', false) : false;
        $activeWebSiloId = false;

        if (!$activeWebSiloId) {
            if ($user) {
                if ($this->activeCustomer()) {
                    $defaultWebSilo = $this->activeCustomer()->webSilo;
                    if ($defaultWebSilo && $defaultWebSilo->is_visible) {
                        $activeWebSiloId = $defaultWebSilo->id;
                    }
                }
            }
            if (!$activeWebSiloId) {
                $query = WebSilo::with('webLandingPageWebSiloJoins');

                if ($this->activeCustomer()) {
//                    $cust_cat_summary = $this->activeCustomer->customerCategory->cust_cat_summary;
//                    $divisions = $this->activeCustomer->divisions->pluck('id');
//                    $query->whereHas('divisionRules', function ($query) use ($divisions) {
//                        $query->whereIn('Divisions.id', $divisions);
//                    })->whereHas('customerCategorySummaryRules', function ($query) use ($cust_cat_summary) {
//                        $query->where('cust_cat_summary', $cust_cat_summary);
//                    });
                } else {
                    $webUrl = WebUrl::current()->first();
                    if (!is_null($webUrl)) {
                        $query->whereHas('webUrls', function ($query) use ($webUrl) {
                            $query->where('id', '=', $webUrl->id);
                        });
                    }
                }
                $result = $query->first();
                if (!is_null($result)) $activeWebSiloId = $result->id;
            }

            if (!$activeWebSiloId) $activeWebSiloId = WebSilo::first()->id;
        }
        $this->activeWebSilo = WebSilo::with('webLandingPageWebSiloJoins')->with('webHierarchy')->find($activeWebSiloId);
        session()->put('b2b.activeWebSiloId', $activeWebSiloId);

        $this->activeWebSiloLoaded = true;
        return $this->activeWebSilo;
    }

    public function loadActiveLanguage()
    {
        $this->activeLanguage = false;

        $activeLanguage = session()->get('b2b.activeLanguage', false);

        if (!isset($activeLanguage)) {
            $activeLanguage = Cookie::get('lang');
            Debugbar::addMessage("Setting language from cookie... . $activeLanguage");
        }

        if ($activeLanguage) {
            if ($this->activeWebSilo()->allowedLanguages()->where('language_code', $activeLanguage)->count()) {
                $this->activeLanguage = $activeLanguage;
            } else {
                $activeLanguage = false;
            }
        }

        if (!$activeLanguage) {
            $webSiloDefaultLanguage = $this->activeWebSilo()->defaultLanguage();
            if (isset($webSiloDefaultLanguage)) {
                $this->activeLanguage = $webSiloDefaultLanguage->language_code;
            } else {
                if (count($this->activeWebSilo()->allowedLanguages)) {
                    $this->activeLanguage = $this->activeWebSilo()->allowedLanguages[0];
                } else {
                    $this->activeLanguage = config('app.locale', 'en_US');
                }
            }
        }
        $existingLang = AvailableLanguage::where('language_code', $this->activeLanguage)->first();
        if (is_null($existingLang)) $this->activeLanguage = AvailableLanguage::DEFAULT_LANG_CODE;
        $oldLanguage = App::getLocale();

        if ($oldLanguage != $this->activeLanguage) {
            Debugbar::addMessage("Setting language from $oldLanguage to {$this->activeLanguage}");
            App::setLocale($this->activeLanguage);
            session()->put('b2b.activeLanguage', $this->activeLanguage);
        }

        if ($this->activeCustomer() != null) {
            $customerLastLanguageUsed = CustomerLastLanguageUsed::where('customer_id', $this->activeCustomer()->id)->first();
            if ($customerLastLanguageUsed != null) {
                if ($customerLastLanguageUsed->language_code != $this->activeLanguage) {
                    $customerLastLanguageUsed->language_code = $this->activeLanguage;
                    $customerLastLanguageUsed->save();
                }
                    
            } else {
                $customerLastLanguageUsed = new CustomerLastLanguageUsed();
                $customerLastLanguageUsed->customer_id = $this->activeCustomer()->id;
                $customerLastLanguageUsed->language_code = $this->activeLanguage;
                $customerLastLanguageUsed->save();
            }
        }
        $this->activeLanguageLoaded = true;

    }

    public function loadActiveWebTranslations()
    {
        $this->activeWebTranslations = false;
        // Fetch the WebGroups.

        // For some reason I kept getting "unable to process request" issues when I was initially calling down the web silo.
        $webGroupTranslations = Translations_WebGroup::where('language', '=', $this->activeLanguage())->get();
        $webCollectionTranslations = Translations_WebCollection::where('language', '=', $this->activeLanguage())->get();
        $activeWebTranslations = ['webCollectionTranslations' => $webCollectionTranslations];
        $this->activeWebTranslations = $activeWebTranslations;
    }

    public function loadActiveLocale()
    {
        $this->activeLocale = false;

        $activeLocale = session()->get('b2b.activeLocale', false);
        if ($activeLocale) {
            $this->activeLocale = $activeLocale;
        } else {
            switch ($this->activeCountry()) {
                case 'GB':
                    $this->activeLocale = 'en_GB';
                    break;
                default:
                    $this->activeLocale = $this->defaultLocale();
                    break;
            }
        }
        // Chris said it was ok to comment this out.
        // Debugbar::addMessage("Setting locale to {$this->activeLocale}");
        $this->activeLocaleLoaded = true;
    }

    public function loadActivePlants() {
        $this->activePlants = false;

        if ($this->activeWebSilo()->is_outdoor_site) {
            $activePlantCodes = [Plant::PLAINVILLE_CODE, Plant::BOZEMAN_CODE];
        } else {
            $activeSegment = $this->activeSegment();
            $customerPlantCode = \Illuminate\Support\Arr::get($activeSegment, 'plant.plant', \Illuminate\Support\Arr::get(Plant::defaultPlant(), 'plant'));
            $activePlantCodes = Plant::inventoryClusterLogicPlants($customerPlantCode);
            // Extra check to make sure Plainville is added to the list because Montreal is technically shut down
            // NOTE: I don't think there will be customers with default plant "Montreal" in M3 -- ntaylor 20200221
//                if ($customerPlantName == 'MONTREAL') {
//                    $activePlantCodes[] = 'PLAINVILLE';
//                }

            // Extra check because the Kaiser microsite technically has some stock in Dallas
            // use 'strpos() !== false' because strpos() returns offset str position if found
            // (so 0 is technically valid) and false if not found at all
            if (strpos($this->activeWebSilo()->slug, 'kaiser') !== false) {
                $activePlantCodes[] = Plant::DALLAS_CODE;
            }
        }
        $this->activePlants = Plant::whereIn('plant', $activePlantCodes)->get();
        $this->activePlantsLoaded = true;
    }

    public function loadRewardsProductCategoryGroupDiscounts()
    {
        $rewardsProductCategoryGroupDiscounts = session()->get('b2b.rewardsProductCategoryGroupDiscounts', false);
        $rewardsProductCategoryGroupDiscounts = RewardsProductCategoryGroupDiscount::all();
        $this->rewardsProductCategoryGroupDiscounts = $rewardsProductCategoryGroupDiscounts;
        $this->rewardsProductCategoryGroupDiscountsLoaded = true;
    }

    public function loadActivePromoCodes()
    {
        $this->activePromoCodes = session()->get('b2b.activePromoCodes', false);
        $this->activePromoCodesLoaded = true;
    }

    public function loadActiveSpendingLimit() {
        $this->activeSpendingLimit = 0;
        $currentSpendingLimit = session()->get('b2b.activeSpendingLimit', false);
        if ($currentSpendingLimit !== false) {
            $this->activeSpendingLimit = $currentSpendingLimit;
        } else {
            $user = auth()->user();
            $customer = $this->activeCustomer();
            $webSilo = $this->activeWebSilo();
            $webUserWebSilo = $user->webSilo()->where('websilo_id', $webSilo->id)->first();
            $customerWebSilo = $customer->customerWebSilo;

            $spendingLimit = -1;

            if (!$this->isAliased()) {
                $spendingLimit = Arr::get($webUserWebSilo, 'pivot.spending_limit', -1);
            }

            if ($spendingLimit < 0) {
                $spendingLimit = Arr::get($customerWebSilo, 'spending_limit', -1);
            }

            if ($spendingLimit < 0) {
                $spendingLimit = Arr::get($webSilo, 'default_spending_limit', -1);
            }

            $this->activeSpendingLimit = $spendingLimit;
            session()->put('b2b.activeSpendingLimit', $spendingLimit);
        }
        $this->activeSpendingLimitLoaded = true;
    }

    public function loadActiveMinimumOrder() {
        $this->activeMinimumOrder = 0;
        $currentMinimumOrder = session()->get('b2b.activeMinimumOrder', false);
        if ($currentMinimumOrder !== false) {
            $this->activeMinimumOrder = $currentMinimumOrder;
        } else {
            $user = auth()->user();
            $customer = $this->activeCustomer();
            $webSilo = $this->activeWebSilo();
            $webUserWebSilo = $user->webSilo()->where('websilo_id', $webSilo->id)->first();
            $customerWebSilo = $customer->customerWebSilo;

            $minimumOrder = -1;

            if (!$this->isAliased()) {
                $minimumOrder = Arr::get($webUserWebSilo, 'pivot.minimum_order', -1);
            }

            if ($minimumOrder < 0) {
                $minimumOrder = Arr::get($customerWebSilo, 'minimum_order', -1);
            }

            if ($minimumOrder < 0) {
                $minimumOrder = Arr::get($webSilo, 'default_minimum_order', -1);
            }

            $this->activeMinimumOrder = $minimumOrder;
            session()->put('b2b.activeMinimumOrder', $minimumOrder);
            $this->activeMinimumOrderLoaded = true;
        }
    }
//  ******************************** LOAD ********************************


//  ******************************** SETTERS ********************************
    public function setActiveShippingAddress($activeShippingAddressId = false)
    {
        if ($activeShippingAddressId) {
            session()->put('b2b.activeShippingAddressId', $activeShippingAddressId);
        } else {
            session()->forget('b2b.activeShippingAddressId');
        }
        $this->loadActiveShippingAddress();
    }

    public function setActiveBillingCustomer($billingCustomerId = false)
    {
        if ($billingCustomerId) {
            session()->put('b2b.activeBillingCustomerId', $billingCustomerId);
        } else {
            session()->forget('b2b.activeBillingCustomerId');
        }
        $this->loadActiveBillingCustomer();
    }

    public function setActiveCarrier($activeCarrierId = false)
    {
        if ($activeCarrierId) {
            session()->put('b2b.activeCarrierId', $activeCarrierId);
        } else {
            session()->forget('b2b.activeCarrierId');
        }
        $this->loadActiveCarrier();
    }

    public function setActiveRate($activeRate = false)
    {
        if ($activeRate) {
            session()->put('b2b.activeRate', $activeRate);
        } else {
            session()->forget('b2b.activeRate');
        }
        $this->activeRate = $activeRate;
    }

    public function setActiveLanguage($language)
    {
        session()->put('b2b.activeLanguage', $language);
        $this->loadActiveLanguage();
    }

    public function setActiveLocale($locale)
    {
        session()->put('b2b.activeLocale', $locale);
        $this->loadActiveLocale();
    }
    
    public function setActivePromoCodes($promoCode)
    {
        $promoCodes = $this->activePromoCodes();
        if (empty($promoCodes)) {
            $promoCodes = [];
        }
        
        array_push($promoCodes, $promoCode);
        session()->put('b2b.activePromoCodes', $promoCodes);
        $this->loadActivePromoCodes();
    }

    public function setRewardsProductCategoryGroupDiscounts($rewardsProductCategoryGroupDiscounts)
    {
        session()->put('b2b.rewardsProductCategoryGroupDiscounts', $rewardsProductCategoryGroupDiscounts);
        $this->rewardsProductCategoryGroupDiscounts = $rewardsProductCategoryGroupDiscounts;
    }
//  ******************************** SETTERS ********************************


//  ******************************** GETTERS ********************************
    /**
     * @return Customer|false|null
     */
    public function activeCustomer()
    {
        if (!$this->activeCustomerLoaded) {
            $this->loadActiveCustomer();
        }
        return $this->activeCustomer;
    }

    public function activeDivision($customer = false) {
        if ($customer === false){
            $customer = $this->activeCustomer();
        }
        $facility = $customer->activeSegment->facilityDivision;
        return $facility->division()->first();
    }

    public function activeBillingCustomer()
    {
        if (!$this->activeBillingCustomerLoaded) {
            $this->loadActiveBillingCustomer();
        }
        return $this->activeBillingCustomer;
    }

    public function activeShippingAddress()
    {
        if (!$this->activeShippingAddressLoaded) {
            $this->loadActiveShippingAddress();
        }
        return $this->activeShippingAddress;
    }

    public function activeCarrier()
    {
        if (!$this->activeCarrierLoaded) {
            $this->loadActiveCarrier();
        }
        return $this->activeCarrier;
    }

    /**
     * @return Rate
     */
    public function activeRate()
    {
        if (!$this->activeRateLoaded) {
            $this->loadActiveRate();
        }
        return $this->activeRate;
    }

    public function activeCountry($shippingAddress = null)
    {
        if ($shippingAddress === null) $shippingAddress = $this->activeShippingAddress();
        if ($shippingAddress) return $shippingAddress->country;

        return null;
    }

    public function activeCountryVATTax() {
        if ($this->activeCountry() != null) {
            $taxes = VATTax::where('country', $this->activeCountry())
                ->whereRaw("(effective_date <= date(now()) or effective_date = '0000-00-00')")
                ->whereRaw("(expiration_date >= date(now()) or expiration_date = '0000-00-00')")
                ->get();
            return $taxes->count() > 0;
        }
        return null;
    }

    public function activePriceList()
    {
        $activePriceList = 'Catalog';
        if ($this->activeCustomer()) {
            $customerPriceList = $this->activeCustomer()->default_price_list;
            if ($customerPriceList) $activePriceList = $customerPriceList;
        }

        return $activePriceList;
    }

    public function activeCurrency()
    {
        $activeCurrency = 'USD';

        if ($this->activeCustomer()) {
            $activeCurrency = $this->activeCustomer()->currency;
        }

        return $activeCurrency;
    }

    public function activeWebHierarchy()
    {
        return $this->activeWebSilo()->webHierarchy;
    }

    /** @return WebSilo */
    public function activeWebSilo()
    {
        if (!$this->activeWebSiloLoaded) {
            $this->loadActiveWebSilo();
        }
        return $this->activeWebSilo;
    }

    public function activeLocale()
    {
        if (!$this->activeLocaleLoaded) {
            $this->loadActiveLocale();
        }
        return $this->activeLocale;
    }

    public function activeLanguage()
    {
        if (!$this->activeLanguageLoaded) {
            $this->loadActiveLanguage();
        }
        return $this->activeLanguage;
    }

    public function activeLanguageImageUrl() {
        $allowedLanguage = $this->activeWebSilo()->allowedLanguages()->where('language_code', $this->activeLanguage())->first();
        if (isset($allowedLanguage) && !empty($allowedLanguage->pivot->icon_override_id)) {
            return webAssetUrl($allowedLanguage->pivot->icon_override_id);
        } else {
            return '/images/'.$this->activeLanguage().'.png';
        }
    }

    public function activeSegment($customer = null) {
        if (!$this->activeSegmentLoaded) {
            $this->loadActiveSegment();
        }
        if ($customer === null) $customer = $this->activeCustomer();

        return Arr::get($customer, 'activeSegment');
    }

    public function activePlant($customer = null) {
        $activePlant = null;
        $activeSegment = $this->activeSegment($customer);

        $activePlant = Arr::get($activeSegment, 'plant', Plant::defaultPlant());

        return $activePlant;
    }

    public function activePlants() {
        if (!$this->activePlantsLoaded) {
            $this->loadActivePlants();
        }
        return $this->activePlants;
    }

    public function activePlantNames() {
        $plantNames = [];
        foreach ($this->activePlants() as $activePlant) {
            $plantNames[] = $activePlant->plant;
        }
        return $plantNames;
    }

//    public function activePromoCode()
//    {
//        return $this->activePromoCode;
//    }
    public function activePromoCodes()
    {
        if (!$this->activePromoCodesLoaded) {
            $this->loadActivePromoCodes();
        }
        return $this->activePromoCodes;
    }

    //todo confirm if these are customer specific???
    public function rewardsProductCategoryGroupDiscounts()
    {
        if (!$this->rewardsProductCategoryGroupDiscountsLoaded) {
            $this->loadRewardsProductCategoryGroupDiscounts();
        }
        return $this->rewardsProductCategoryGroupDiscounts;
    }

    public function activeWebTranslations()
    {
        return $this->activeWebTranslations;
    }

    public function activeSpendingLimit() {
        if (!$this->activeSpendingLimitLoaded) {
            $this->loadActiveSpendingLimit();
        }
        return $this->activeSpendingLimit;
    }

    public function activeMinimumOrder() {
        if (!$this->activeMinimumOrderLoaded) {
            $this->loadActiveMinimumOrder();
        }
        return $this->activeMinimumOrder;
    }

//  ******************************** GETTERS ********************************


//  ******************************* ALIASING ********************************
    public function aliasAs($customerId = false)
    {
        session()->forget('b2b');

        if ($customerId) {
            session()->put('b2b.aliasedCustomerId', $customerId);
        }

        $this->loadActiveCustomer();
    }

    public function isAliased()
    {
        return (session()->get('b2b.aliasedCustomerId', false));

    }

    public function stopAliasing()
    {
        $this->aliasAs(false);
    }
//  ******************************* ALIASING ********************************


//  ****************************** FORMATTING *******************************
    /**
     * @param bool $currencyCode
     * @return string
     */
    public function currencySymbol($currencyCode = false) {
        if ($currencyCode === false) {
            $currencyCode = $this->activeCurrency();
        }

        switch ($currencyCode) {
            case 'GBP': {
                return '£';
            }
            case 'EUR': {
                return '€';
            }
            default: {
                return '$';
            }
        }
    }

    /**
     * @param $price
     * @param int $decimals
     * @param bool $symbol
     * @return string
     */
    public function formatPrice($price, $decimals = 2, $symbol = false, $currency = false) {
        if ($symbol === false) {
            $symbol = $this->currencySymbol();
        }

        if ($currency === false) {
            $currency = $this->activeCurrency();
        }

        if ($decimals == -1) {
            if ($price == round($price)) $decimals = 0;
            else $decimals = 2;
        }

        if (is_string($price)) {
            return $price;
        } else {
            // one day NumberFormatter (requires php7.1-intl) will work here...
//            $price = number_format($price, $decimals);
//            $numberFormatter = new NumberFormatter($this->activeLanguage(), NumberFormatter::CURRENCY);
//            return $numberFormatter->formatCurrency($price, $this->activeCurrency());
            if ($currency == 'EUR') {
                return number_format($price, $decimals, ",", ".") . $symbol;
            } else {
                return $symbol . number_format($price, $decimals, ".", ",");
            }
        }
    }

    public function dateFormatString($division = false) {
        $format = 'm/d/Y';

        if ($division === false && $this->activeSegment()) {
            $facilityDivisionRecord = $this->activeSegment()->facilityDivision;
            if (isset($facilityDivisionRecord)) {
                $division = $facilityDivisionRecord->division;
            }
        }

        if ($division !== false) {
            if (strpos($division, Division::UK_CODE)) {
                $format = 'd/m/Y';
            }
        }

        return $format;
    }

    public function formatDate($dateString) {
        $format = $this->dateFormatString();
        $time = strtotime($dateString);
        $result = date($format,$time);
        return $result;

    }
//  ****************************** FORMATTING *******************************



    public function isOrderable(Model $model)
    {
        if ($model instanceof WebFamily) return $this->isWebFamilyOrderable($model);
        else if ($model instanceof WebPart) return $this->isWebPartOrderable($model);
        else if ($model instanceof Part) return $this->isPartOrderable($model);

        return false;
    }

    public function isWebFamilyOrderable(WebFamily $webFamily)
    {
        $isOrderable = true;
        foreach ($webFamily->visibleWebParts as $webPart) {
            if (!$this->isWebPartOrderable($webPart)) $isOrderable = false;
        }
        return $isOrderable;
    }

    public function isWebPartOrderable(WebPart $webPart)
    {
        $part = $webPart->part;
        if (is_null($part)) return false;
        return $this->isPartOrderable($part);
    }

    public function isPartOrderable(Part $part)
    {
        if ($this->activeCountry() != 'US' && $this->activeCountry() != 'GB') {
            if ($part->isRX) return false;
        }
        return true;
    }

    public function allowCreditCardOrders()
    {
        if($this->activeCountry() != 'GB') {
            return (bool)
                (!$this->activeRate() || !$this->activeRate()->isError())
                &&
                $this->orderCreditEligible();
        }

        return false;
    }

    public function orderCreditEligible(){
        return (bool)
            $this->activeWebSilo()->allow_credit_cards &&
            $this->isBillingDirect();
    }

    public function allowDirectBilling()
    {
        if (!$this->isBillingDirect() && !$this->allowBillingChange()) {
            return false;
        }

        return (bool)$this->activeWebSilo()->allow_direct_billing;
    }

    public function allowGroupBilling()
    {
        if ($this->isBillingDirect() && !$this->allowBillingChange()) {
            return false;
        }
        return (bool)$this->activeWebSilo()->allow_group_billing;
    }

    public function allowBillingChange()
    {
        if ($this->activeCountry() == 'GB') return false;
        else return true;
    }

    public function allowPOOrders()
    {
        $terms = \Illuminate\Support\Arr::get($this->activeBillingCustomer(), 'activeSegment.def_pmt_terms');
        return !in_array($terms, ['CC', 'CR CARD', 'CR CARD+BAL']);
    }

    public function isPONumberRequired() {
        return $this->activeWebSilo()->po_number_required;
    }

    public function creditProfiles() {
        $creditProfiles = [];
        try {
            $cenposWrapper = CenposWrapper::create('ecommerce', b2b()->activeBillingCustomer()->currency);
            $creditProfiles = $cenposWrapper->getCreditProfiles(b2b()->activeBillingCustomer()->cust_no);
        } catch (BillingNotActiveForCurrencyException $e) {
            Log::warning('Tried to fetch credit profiles for customer ' . b2b()->activeBillingCustomer()->cust_no . ': ' . $e->getMessage(), $e->getTrace());
        } catch (\Exception $e) {
            Log::warning('Tried to fetch credit profiles for customer ' . b2b()->activeBillingCustomer()->cust_no . ': ' . $e->getMessage(), $e->getTrace());
        }
        return $creditProfiles;
    }

    public function isRewardsEnabled($webSilo = null)
    {
        if (!config('rewards.rewardsEnabled')) return false;
        if (config('rewards.rewardsEnabled') == 'always') return true;
        if ($webSilo === null) $webSilo = $this->activeWebSilo();
        return (bool)$webSilo->allow_rewards;
    }

    public function isBillingDirect($customer = null)
    {
        if ($customer === null){
            return $this->activeCustomer()->id == $this->activeBillingCustomer()->id;
        } else {
            return $customer->billsDirect;
        }
    }

    public function isCustomerTaxable($customer = null)
    {
        if ($customer === null) $customer = $this->activeBillingCustomer();
        return Arr::get($customer, 'activeSegment.cust_txbl', false);
    }

    public function availableShippingAddresses() {
        $defaultAddress = $this->activeCustomer()->default_shipping_address;
        $addresses = collect([$defaultAddress]);
        if ($this->activeWebSilo()->allow_drop_shipping) {
            $activeSegment = $this->activeSegment();
            if (isset($activeSegment) && !empty($activeSegment->def_ship_from)) {
                $addresses =
                    $activeSegment
                        ->shipping_addresses()
                        ->hasShipToProfile()
                        ->isActive()
                        ->get()
                        ->sort(function ($a, $b) use ($defaultAddress) {
                            if ($a->id == $defaultAddress->id) return -1;
                            if ($b->id == $defaultAddress->id) return 1;
                            return strcmp($a->cust_name, $b->cust_name);
                        })
                ;
            }
        }

        return $addresses;
    }

    public function isValidPromoCode($promoCode) {
        // does promo code even belong to an existing codeTrigger
        $codeTriggers = PromotionCodeTrigger::where('code', '=', $promoCode)->get();
        if (isset($codeTriggers)) {
            foreach($codeTriggers as $codeTrigger) {
                $promotion = $codeTrigger->promotion()->first();
                if ($this->existsInActivePromos($promotion)) {
                    return true;
                }
            }
        }
        return false;
    }

    // TODO: Come up with a better name for this, probably.
    // Checks for promotions that are code triggered, but still have un-triggered triggers (ugh)
    // This requires a seperate display for the checkout controller to indicate this when the code is input.
    // We could possible expand this to display the kind of promotion it is and the specifics if needed.
    public function nonTriggeredCodeTriggeredPromotions($customerTriggeredPromotions){
        $nonTriggeredCodeTriggeredPromotions = [];
        $billingCustomer = b2b()->activeBillingCustomer();
        $webCartItems = WebCart::getWebCartItems(auth()->user(), false);
        $distincts = WebCart::getDistinctPartInfo($webCartItems);
        $distinctPartInfo = $distincts['distinctPartInfo'];
        
        foreach($customerTriggeredPromotions as $customerTriggeredPromotion){
            $codeTrigger = $customerTriggeredPromotion->codeTriggers()->first();
            if(is_null($codeTrigger)){
                continue;
            }

            if($codeTrigger->isTriggered($billingCustomer, $webCartItems)){
                foreach ($customerTriggeredPromotion->triggers as $trigger) {
                    if ($trigger->trigger_type == 'codeTrigger') {
                        continue;
                    }
                    if (!$trigger->details->isTriggered($billingCustomer, $webCartItems, $distinctPartInfo)) {
                        $nonTriggeredCodeTriggeredPromotions[$codeTrigger->code] = $customerTriggeredPromotion;
                        return $nonTriggeredCodeTriggeredPromotions;
                    }
                }
            }

        }

        return $nonTriggeredCodeTriggeredPromotions;

    }

    private function existsInActivePromos($promotion) {
        foreach (Promotion::getActivePromotions(null, false) as $activePromotion) {
            if ($activePromotion->id === $promotion->id) return true;
        }
        return false;
    }
    
    public function requiresTnC() {
        return $this->activeWebSilo()->terms_cond_required;
    }

//  ******************************** RATE SHOP ********************************

    /**
     * @return Collection|Rate[]
     */
    public function rateShop($cartStatus = false, $setActiveRate = true, $activeRate = null, $webSilo = null, $soldToCustomer = null, $shippingAddress = null)
    {
        if ($webSilo === null) $webSilo = $this->activeWebSilo();

        $oldRates = [];
        if ($webSilo->allow_rate_shopping && !b2b()->doesCartHaveBannedProductRestrictions()) {
            $oldRates = $this->oldRateShop($cartStatus, $soldToCustomer, $shippingAddress);
        }

        $siloMethods = $webSilo->allowedDeliveryMethods();
        if (count($siloMethods) > 0) {
            foreach ($oldRates as $index => $oldRate) {
                if (!in_array(\Illuminate\Support\Arr::get($oldRate, 'delivery_method'), $siloMethods)) {
                    unset($oldRates[$index]);
                }
            }
        }

        if ($activeRate === null) $activeRate = $this->activeRate();
        $activeRateCode = ($activeRate) ? $activeRate->code() : false;
        $activeRateTerm = ($activeRate) ? $activeRate->term() : false;
        $wasRateSelected = false;

        if ($shippingAddress === null)  $shippingAddress = $this->activeShippingAddress();

        $rates = new Collection();
        foreach ($oldRates as $key => $oldRate) {
            $method = \Illuminate\Support\Arr::get($oldRate, 'delivery_method', false);
            
            if ($method === false) continue;

            $term = \Illuminate\Support\Arr::get($oldRate, 'delivery_term', \Illuminate\Support\Arr::get($shippingAddress, 'def_delivery_term'));
            $rate = \Illuminate\Support\Arr::get($oldRate, 'rate');
            if (is_null($rate) || $rate < 0) continue;

            $isDefault = \Illuminate\Support\Arr::get($oldRate, 'is_default', false);
            $discount = \Illuminate\Support\Arr::get($oldRate, 'discounted_rate', 0);
            $tax = \Illuminate\Support\Arr::get($oldRate, 'tax_amount', 0);

            $isFreightAccount = \Illuminate\Support\Arr::get($oldRate, 'is_freight_account', false);

            $isGermanDomestic = Arr::get($oldRate, 'is_german_domestic', false);

            $rate = new Rate($method, $term, $rate, $isDefault, $discount, $tax, $isFreightAccount, $isGermanDomestic);
            $overrideText = Arr::get($oldRate, 'carrier_name', '') . ' - ' . Arr::get($oldRate, 'carrier_desc', '');
            $rate->setText($overrideText);

            if ($method == $activeRateCode && $term == $activeRateTerm) {
                $rate->selected = true;
                $wasRateSelected = true;
            }
            $rates->put($key, $rate);
        }

        $rates = $rates->sort(function (Rate $a, Rate $b) {
            $aSpecial = $a->isDefault() || $a->isDiscounted();
            $bSpecial = $b->isDefault() || $b->isDiscounted();

            if ($aSpecial && !$bSpecial) return -1; //a is special but b isn't, a goes first
            if (!$aSpecial && $bSpecial) return 1; //b is special but a isn't, b goes first

            //both or neither are special
            if ($a->rate() > $b->rate()) return 1;
            if ($a->rate() < $b->rate()) return -1;

            return 0;
        });

        if ($rates->count() == 0) {
            $rates->put('-1', new Rate(null, null, -1, true));
        }

        if ($activeRateCode === false || $wasRateSelected === false) {
            $firstRate = $rates->shift();
            $firstRate->selected = true;
            if ($setActiveRate === true){
                $this->setActiveRate($firstRate);
            }
            $rates->prepend($firstRate);
        }

        return $rates;
    }

    protected function rateShopGroupCode($shippingAddress = null)
    {
        if ($shippingAddress === null) $shippingAddress = $this->activeShippingAddress();
        $countryString = \Illuminate\Support\Arr::get($shippingAddress, 'country');
        return $countryString != null && $countryString == "CA" ? 2 : 3;
    }

    protected function getFromCode($shippingAddressObj, $groupCode)
    {
        if ($groupCode === 2) {
            return \Illuminate\Support\Arr::get($shippingAddressObj, 'plant.plantAddress.canada_zip');
        } else {
            return \Illuminate\Support\Arr::get($shippingAddressObj, 'plant.plantAddress.zip');
        }
    }

    public function getMultiWarehouseShipFrom($cartItems){
        $segment = b2b()->activeCustomer()->activeSegment;
        $atpRequestData = [];
        foreach ($cartItems as $cartItem) {
            $part = $cartItem['part'];
            $atpRequestData[] = $part->part_no;
        }

        $vegasResponse = WebPart::fetchAllItemsStockInfo($atpRequestData, 'U31');

        $allInStock = true;
        $allStockable = true;
        foreach($vegasResponse as $atp) {
            if($atp['stock'] == 0){
                $allInStock = false;
            }

            if(!$atp['stockable']){
                $allStockable = false;
            }
        }

        if($allInStock){
            return [
                'plant' => $segment->plant->plant,
                'id' => $segment->plant->id
            ];
        }else{
            $allInStock = true;
            $plant = Plant::where('plant', '=', 'U11')->where('deleted_at', '=', '0000-00-00 00:00:00')->first();

            $plainvilleResponse = WebPart::fetchAllItemsStockInfo($atpRequestData, $plant->plant);
            foreach($plainvilleResponse as $atp) {
                if($atp['stock'] == 0){
                    $allInStock = false;
                }

                if(!$allInStock){
                    break;
                }
            }

            if($allInStock || (!$allInStock && !$allStockable)){
                return [
                    'plant' => $plant->plant,
                    'id' => $plant->id
                ];
            }
        }

        return [
            'plant' => $segment->plant->plant,
            'id' => $segment->plant->id
        ];
    }

    /**
     * @param $carrierName
     * @param $apiCode
     * @param $carrierDesc
     * @param $rate
     * @param $discRate
     * @param $deliveryDateTime
     * @param $method
     * @param $term
     * @param $isDefault
     * @param $isFreightAcct
     * @param $isGermanDomestic
     * @param $tax
     * @return array
     */
    private function buildOldRateEntry ($carrierName, $apiCode, $carrierDesc, $rate, $discRate, $deliveryDateTime, $method, $term, $isDefault, $isFreightAcct, $isGermanDomestic, $tax = 0): array {
        return [
            'carrier_name'          => $carrierName,
            'api_code'              => $apiCode,
            'carrier_desc'          => $carrierDesc,
            'rate'                  => $rate,
            'discounted_rate'       => $discRate,
            'delivery_datetime'     => $deliveryDateTime,
            'delivery_method'       => $method,
            'delivery_term'         => $term,
            'is_default'            => $isDefault,
            'is_freight_account'    => $isFreightAcct,
            'is_german_domestic'    => $isGermanDomestic,
            'tax_amount'            => $tax,
        ];
    }

    /**
     * @return array
     */
    protected function oldRateShop($cartStatus = false, $soldToCustomer = null, $shippingAddress = null)
    {
        if ($soldToCustomer === null) $soldToCustomer = $this->activeCustomer();
        $customerSegment = $soldToCustomer->activeSegment;

        if ($shippingAddress === null) $shippingAddress = $this->activeShippingAddress();
        
        if ($cartStatus === false){
            $cartStatus = App::make('cartSingleton')->getCartStatus();
        }

        $defaultDeliveryMethodCode = Arr::get($shippingAddress, 'def_delivery_method');
        $defaultDeliveryTermCode = Arr::get($shippingAddress, 'def_delivery_term');

        /* GERMAN DIVISION SHIPPING LOGIC */
        if ($this->activeDivision()->division_trimmed == Division::DE_CODE) {
            if ($this->activeCustomer()->is_de_domestic) {
                try {
                    $apiClient = null;
                    $m3Request = new M3Request();
                    if (empty($m3Request->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{
                        $apiClient = $m3Request->client;
                    }

                    $langCode = strtoupper(substr($this->activeLanguage(), 0, 2));
                    $serviceChargeCode = $this->activeCustomer()->getServiceChargeCodeForWeb();

                    $addressId = $shippingAddress->getAddrNoId();
                    if (trim($serviceChargeCode) == "" && $addressId == CustomerShippingAddress::GERMAN_DIRECT_DELIVERY_ADDRESS_ID) {
                        $serviceChargeCode = Customer::GERMAN_DOMESTIC_WEB_SERVICE_CHARGE_CODE;
                    }

                    $freightServiceChargeResponse = $apiClient->get(
                        'v4/getFreightServiceCharge',
                        [
                            'query' => [
                                'service_charge_code' => $serviceChargeCode,
                                'currency_code' => $this->activeCustomer()->currency,
                                'order_amount' => $cartStatus['discountedTotal'],
                                'language_code' => $langCode,
                                'requestFrom' => 'b2b',
                            ],
                            'headers' => ['Accept' => 'application/json']
                        ])
                        ->getBody()
                        ->getContents();

                    $freightServiceChargeResponse = json_decode($freightServiceChargeResponse, true);

                    if (!isset($freightServiceChargeResponse['status'])) {
                        Log::critical('API failed to return status of getRates call');
                        throw new \Exception('API failed to return status of getFreightServiceCharge call');
                    }

                    if ($freightServiceChargeResponse['status'] == 'failure') {
                        Log::critical('API returned status "failure" for getRates call: ' . $freightServiceChargeResponse['details']);
                        throw new \Exception('API returned status "failure" for getFreightServiceCharge call');
                    }

                    $orderLimit = $freightServiceChargeResponse['orderLimit']; // for reference
                    $serviceCharge = $freightServiceChargeResponse['serviceCharge'];
                    $serviceChargeName = $freightServiceChargeResponse['serviceChargeName'];

                    $shippingVatAmt = 0;
                    if ($this->isCustomerTaxable() && $this->activeCountryVATTax()) {
                        $deVatTax1 = VATTax::where('country', $this->activeCountry())
                            ->whereRaw("(effective_date <= date(now()) or effective_date = '0000-00-00')")
                            ->whereRaw("(expiration_date >= date(now()) or expiration_date = '0000-00-00')")
                            ->where('vat_code', '=', 1)
                            ->first();
                        if (isset($deVatTax1)) {
                            $vatPercentage = $deVatTax1->tax_percentage;
                            $shippingVatAmt = $serviceCharge * ($vatPercentage / 100);
                        }
                    }

                    $germanDomesticShippingOptions = [];
                    $germanDomesticShippingOptions[$defaultDeliveryMethodCode] =
                        $this->buildOldRateEntry(
                            '',
                            null,
                            $serviceChargeName,
                            $serviceCharge,
                            0,
                            null,
                            $defaultDeliveryMethodCode,
                            $defaultDeliveryTermCode,
                            true,
                            false,
                            true,
                            $shippingVatAmt
                        );

                    return $germanDomesticShippingOptions;
                } catch (\Exception $e) {
                    Log::warning('Exception caught while getting freight service charges for German domestic, returning []: ' . $e->getMessage(), $e->getTrace());
                    return [];
                }
            } else {
                return [['id' => -1, 'text' => '']];
            }
        }

        $toCountry = Arr::get($shippingAddress, 'country');
        $groupCode = $this->rateShopGroupCode($shippingAddress);
        $fromCode = $customerSegment->plant->plantAddress->zip;
        $fromCountry = $customerSegment->plant->plantAddress->country;

        $includedDeliveryMethods = null;
        $eligibleDeliveryMethods = [];
        $combinedShipVia = [];

        $cartItems = $cartStatus['items'];
        $excludedItems = [];
        $rewardsItems = [];
        $cartTotal = 0;
        $weights = 0;
        $excludedWeights = 0;
        $rewardsWeights = 0;
        $volume = 0;
        $excludedVolume = 0;
        $rewardsVolume = 0;
        $hideAir = false;
        foreach ($cartItems as $cartItem) {
            $quantity = $cartItem['quantity'];
            $part = $cartItem['part'];
            if ($part->isRewardsExcluded()) {
                $excludedItems[$cartItem['id']] = $cartItem;
            } else {
                $rewardsItems[$cartItem['id']] = $cartItem;
            }

            if (in_array($cartItem, $excludedItems)) {
                if (!isset($excludedWeights)) {
                    $excludedWeights = $part->weight_lbs * $quantity;
                } else {
                    $excludedWeights += $part->weight_lbs * $quantity;
                }
            } else {
                if (!isset($rewardsWeights)) {
                    $rewardsWeights = $part->weight_lbs * $quantity;
                } else {
                    $rewardsWeights += $part->weight_lbs * $quantity;
                }
            }

            if (!isset($weights)) {
                $weights = $part->weight_lbs * $quantity;
            } else {
                $weights += $part->weight_lbs * $quantity;
            }

            if (in_array($cartItem, $excludedItems)) {
                $excludedVolume += $part->vol_ci * $quantity;
            } else {
                $rewardsVolume += $part->vol_ci * $quantity;
            }
            $volume += $part->vol_ci * $quantity;

            //TODO: This is only a temp solution. we really don't want to parse the part description string for the hazmat code. should be coming from m3
            $hideAir = $hideAir || strpos($part->part_desc, "ORM-D") !== false;
            $cartTotal += $cartItem['extendedPrice'];
        }

        if ($weights > 0) {
            if (b2b()->activeDivision()->division_trimmed == Division::AU_CODE) {
                /* AUS REQUESTED NO RATE SHOPPING */
                return [['id' => -1, 'text' => trans('app.unable_to_calculate_shipping_rates')]];
            } else if (b2b()->activeDivision()->division_trimmed == Division::UK_CODE) {
                /* UK REQUESTED NO RATE SHOPPING */
                return [['id' => -1, 'text' => trans('app.unable_to_calculate_shipping_rates')]];
            } else {
                // if customer has their own freight account and the default term is C00/T00 or C03/T03
                // then set default shipping option to $0 or <Hilco Handling Fee> ($3) so they don't get double-billed
                $onlyShowFreightAccountOption = false;
                if (!empty($shippingAddress->def_ship_via) &&
                    (in_array($defaultDeliveryTermCode, DeliveryTerm::getBillCollectTerms()) ||
                        in_array($defaultDeliveryTermCode, DeliveryTerm::getBillThirdPartyTerms()))) {

                    $billReceiverRate = 0;
                    if (substr_compare($defaultDeliveryTermCode, "03", -2) === 0) {
                        $billReceiverRate = Rate::getHandlingFee();
                    }

                    $billReceiverOptions = [];

                    // first (and default) shipping option should be the Bill Receiver option
                    $billReceiverOptions[$defaultDeliveryMethodCode] =
                            $this->buildOldRateEntry(
                                '',
                                null,
                                'Bill Receiver',
                                $billReceiverRate,
                                0,
                                null,
                                $defaultDeliveryMethodCode,
                                $defaultDeliveryTermCode,
                                true,
                                true,
                                false
                            );

                    // then gather the rest of the options based on their default carrier
                    $defaultCarrier = DeliveryMethod::whereDeliveryMethodCode($defaultDeliveryMethodCode)->first()->carrier;
                    $billReceiverMethods = DeliveryMethod::isForBillReceiver()->whereCarrier($defaultCarrier)->get();
                    foreach ($billReceiverMethods as $billReceiverMethod) {
                        // don't overwrite the default option that was already added
                        if ($defaultDeliveryMethodCode != $billReceiverMethod->delivery_method_code) {
                            $billReceiverOptions[$billReceiverMethod->delivery_method_code] =
                                $this->buildOldRateEntry(
                                    $billReceiverMethod->carrier,
                                    null,
                                    $billReceiverMethod->billrcvr_desc,
                                    $billReceiverRate,
                                    0,
                                    null,
                                    $billReceiverMethod->delivery_method_code,
                                    $defaultDeliveryTermCode,
                                    false,
                                    true,
                                    false
                                );
                        }
                    }

                    return $billReceiverOptions;
                }

                //The list of possible methods to show to the customer
                $includedDeliveryMethods = RateShopGroup::where('group_code', '=', $groupCode)
                    ->pluck('delivery_method')
                    ->toArray();

                if(($defaultDeliveryMethodCode == 'U21' && $defaultDeliveryTermCode == 'F00')
                    || ($defaultDeliveryMethodCode == 'PGW' && $defaultDeliveryTermCode == 'L00')
                    || ($defaultDeliveryMethodCode == 'U03' && $defaultDeliveryTermCode == 'D30')
                    && !in_array($defaultDeliveryMethodCode, $includedDeliveryMethods)){

                    $eligibleDeliveryMethods[] = $defaultDeliveryMethodCode;
                }

                if ($defaultDeliveryMethodCode != null && $defaultDeliveryTermCode != null && $defaultDeliveryMethodCode == 'U39' && $defaultDeliveryTermCode == 'D00') {
                    if (($key = array_search('U03', $includedDeliveryMethods)) !== false) {
                        unset($includedDeliveryMethods[$key]);
                    }else if(($key = array_search('U03', $eligibleDeliveryMethods)) !== false){
                        unset($eligibleDeliveryMethods[$key]);
                    }
                } else {
                    if (($key = array_search('U39', $includedDeliveryMethods)) !== false) {
                        unset($includedDeliveryMethods[$key]);
                    }else if(($key = array_search('U03', $eligibleDeliveryMethods)) !== false){
                        unset($eligibleDeliveryMethods[$key]);
                    }
                }

                if ($hideAir) {
                    $airCodes = DB::connection()
                        ->table('RateShopAPIDetails')
                        ->whereIn('delivery_method', array_merge($includedDeliveryMethods, $eligibleDeliveryMethods))
                        ->where('is_air', '=', true)
                        ->select('delivery_method')
                        ->get();

                    foreach ($airCodes as $airCode) {
                        if (($key = array_search($airCode->delivery_method, $includedDeliveryMethods)) !== false) {
                            unset($includedDeliveryMethods[$key]);
                        }else if(($key = array_search($airCode->delivery_method, $eligibleDeliveryMethods)) !== false){
                            unset($eligibleDeliveryMethods[$key]);
                        }
                    }
                }

                try {
                    $client = null;
                    $guzzle = new M3Request();
                    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;
                    }

                    if(count($excludedItems)){
                        $excludedRates = json_decode(
                            $client->get(
                                'v4/' . 'getRates',
                                [
                                    'query' => [
                                        'requestFrom' => 'b2b',
                                        'eligibleMethods' => json_encode($eligibleDeliveryMethods),
                                        'groupCode' => $groupCode,
                                        'weight' => $excludedWeights,
                                        'volume' => $excludedVolume,
                                        'toCode' => $shippingAddress->postal_cd,
                                        'toCountry' => $toCountry,
                                        'fromCode' => $fromCode,
                                        'fromCountry' => $fromCountry
                                    ],
                                    'headers' => ['Accept' => 'application/json']
                                ])->getBody()->getContents(),
                            true
                        );
                        if (!isset($excludedRates['status'])) {
                            Log::critical('API failed to return status of getRates call for excluded rates.');
                        } else if ($excludedRates['status'] == 'failure') {
                            Log::critical('API returned status "failure" for getRates call for excluded rates.');
                        } else {
                            if (isset($excludedRates['details']) && is_array($excludedRates['details'])) {
                                $excludedShipVia = $excludedRates['details'];
                            } else {
                                Log::error("Error fetching rewards excluded rates. Rewards may not be included in returned rates");
                            }
                        }
                        if(count($rewardsItems)){
                            $rewardsRates = json_decode($client->get('v4/' . 'getRates', ['query' => ['requestFrom' => 'b2b', 'eligibleMethods' => json_encode($eligibleDeliveryMethods), 'groupCode' => $groupCode,'weight' => $rewardsWeights, 'volume' => $rewardsVolume, 'toCode' => $shippingAddress->postal_cd, 'toCountry' => $toCountry, 'fromCode' => $fromCode, 'fromCountry' => $fromCountry], 'headers' => ['Accept' => 'application/json']])->getBody()->getContents(), true);
                            if (!isset($rewardsRates['status'])) {
                                Log::critical('API failed to return status of getRates call for rewards rates.');
                            } else if ($rewardsRates['status'] == 'failure') {
                                Log::critical('API returned status "failure" for getRates call for rewards rates.');
                            } else {
                                if (isset($rewardsRates['details']) && is_array($rewardsRates['details'])) {
                                    $rewardsShipVia = $rewardsRates['details'];
                                } else {
                                    Log::error("Error fetching rewards eligible rates. Rewards may not be included in returned rates");
                                }
                            }
                        }
                    }

                    $rates = json_decode($client->get('v4/' . 'getRates', ['query' => ['requestFrom' => 'b2b', 'eligibleMethods' => json_encode($eligibleDeliveryMethods), 'groupCode' => $groupCode,'weight' => $weights, 'volume' => $volume, 'toCode' => $shippingAddress->postal_cd, 'toCountry' => $toCountry, 'fromCode' => $fromCode, 'fromCountry' => $fromCountry], 'headers' => ['Accept' => 'application/json']])->getBody()->getContents(), true);

                    if (!isset($rates['status'])) {
                        Log::critical('API failed to return status of getRates call');
                        throw new \Exception('API failed to return status of getRates call');
                    }

                    if ($rates['status'] == 'failure') {
                        Log::critical('API returned status "failure" for getRates call');
                        throw new \Exception('API returned status "failure" for getRates call');
                    }

                    if (!isset($rates['details']) || !is_array($rates['details'])) {
                        Log::critical('API returned empty or null details for getRates call (no rates)');
                        throw new \Exception('API returned empty or null details for getRates call (no rates)');
                    }

                    $shipVia = $rates['details'];
                } catch (\Exception $e) {
                    Log::critical('Exception caught while getting shipping rates, returning []: ' . $e->getMessage());
                    return [];
                }
            }
        }

        if (!isset($excludedShipVia)) $excludedShipVia = [];
        foreach($excludedShipVia as $method => $rateInfo){
            if(!in_array($method, array_merge($includedDeliveryMethods, $eligibleDeliveryMethods)) || is_null($rateInfo['rate'])){
                unset($excludedShipVia[$method]);
            }
        }

        if (!isset($rewardsShipVia)) $rewardsShipVia = [];
        foreach($rewardsShipVia as $method => $rateInfo){
            if(!in_array($method, array_merge($includedDeliveryMethods, $eligibleDeliveryMethods)) || is_null($rateInfo['rate'])){
                unset($rewardsShipVia[$method]);
            }
        }

//        if (isset($shipVia['U21'])) { -- remove this for now, we need to fix rateshop first
//            $shipVia['U21']['rate'] = 0;
//        }

        if (!isset($shipVia)) $shipVia = [];
        foreach($shipVia as $method => $rateInfo){
            if(!in_array($method, array_merge($includedDeliveryMethods, $eligibleDeliveryMethods)) || is_null($rateInfo['rate'])){
                unset($shipVia[$method]);
            }else{
                $shipVia[$method]['is_default'] = false;
                if ($toCountry == 'CA' &&
                    (
                        ($method == 'F60' && $rateInfo['api_code'] == 'INTERNATIONAL_PRIORITY') ||
                        ($method == 'F70' && $rateInfo['api_code'] == 'INTERNATIONAL_ECONOMY')
                    )) {
                    // OK this is a hardcoded 'hack' but Hilco needs us to use their discounted shipping rate for
                    // Fedex international shipping options to Canada (rather than the standard published rate), and
                    // now they also need 30% of that discounted rate added on top (as of 2021-03-17 email
                    // "Fedex Freight Options into Canada" from RJ)
                    $hilcoDiscountedRate = $rateInfo['discounted_rate'];
                    $hilcoExtra30Percent = $hilcoDiscountedRate * 30 / 100;
                    $shipVia[$method]['rate'] = round($hilcoDiscountedRate + $hilcoExtra30Percent, 2);
                }
                $shipVia[$method]['discounted_rate'] = 0;
                if(!isset($combinedShipVia[$method])){
                    $combinedShipVia[$method] = [];
                }

                $combinedShipVia[$method][] = $shipVia[$method];
            }
        }

        $baseRates = collect();
        $excludedBaseRates = collect();
        $rewardsBaseRates = collect();

        if (isset($shipVia['U06'])) {
            $baseRates['U06'] = \Illuminate\Support\Arr::get($shipVia, 'U06.rate', 0);
        }

        if (isset($shipVia['U03'])) {
            $baseRates['U03'] = \Illuminate\Support\Arr::get($shipVia, 'U03.rate', 0);
        } else if (isset($shipVia['U39'])) {
            $baseRates['U39'] = \Illuminate\Support\Arr::get($shipVia, 'U39.rate', 0);
        }

        if (isset($excludedShipVia['U06'])) {
            $excludedBaseRates['U06'] = \Illuminate\Support\Arr::get($excludedShipVia, 'U06.rate', 0);
        }

        if (isset($excludedShipVia['U03'])) {
            $excludedBaseRates['U03'] = \Illuminate\Support\Arr::get($excludedShipVia, 'U03.rate', 0);
        } else if (isset($excludedShipVia['U39'])) {
            $excludedBaseRates['U39'] = \Illuminate\Support\Arr::get($excludedShipVia, 'U39.rate', 0);
        }

        if (isset($rewardsShipVia['U06'])) {
            $rewardsBaseRates['U06'] = \Illuminate\Support\Arr::get($rewardsShipVia, 'U06.rate', 0);
        }

        if (isset($rewardsShipVia['U03'])) {
            $rewardsBaseRates['U03'] = \Illuminate\Support\Arr::get($rewardsShipVia, 'U03.rate', 0);
        } else if (isset($rewardsShipVia['U39'])) {
            $rewardsBaseRates['U39'] = \Illuminate\Support\Arr::get($rewardsShipVia, 'U39.rate', 0);
        }

        $netOrderTotal = $cartStatus['discountedTotal']; //'discounts.rewards', 0) - \Illuminate\Support\Arr::get($cartStatus, 'discounts.promotion', 0);
        $netRewardsOrderTotal = 0;

        if (count($rewardsItems)) {
            if (count($excludedItems)) {
                foreach ($rewardsItems as $rewardsItem) {
                    $netRewardsOrderTotal += $rewardsItem['extendedPrice'];
                }
                $rewardsCarrierCodes = rewards()->carrierCode($netRewardsOrderTotal, $rewardsBaseRates, $cartStatus['rewardsTier']);
            } else {
                $rewardsCarrierCodes = rewards()->carrierCode($netOrderTotal, $baseRates, $cartStatus['rewardsTier']);
            }

            if ($rewardsCarrierCodes) {
                foreach ($rewardsCarrierCodes as $rewardsCarrierCode) {
                    if(in_array($rewardsCarrierCode['base_method'], array_keys($shipVia))) {
                        $carrier = $shipVia[$rewardsCarrierCode['base_method']]['carrier_name'];
                        if (count($excludedItems)) {
                            $baseRate = \Illuminate\Support\Arr::get($rewardsBaseRates, $rewardsCarrierCode['base_method'], 0) +
                                \Illuminate\Support\Arr::get($excludedBaseRates, $rewardsCarrierCode['base_method'], 0);

                            if ($baseRate > 0) {
                                if(!isset($combinedShipVia[$rewardsCarrierCode['delivery_method']])){
                                    $combinedShipVia[$rewardsCarrierCode['delivery_method']] = [];
                                }

                                $combinedShipVia[$rewardsCarrierCode['delivery_method']][] = [
                                    'carrier_name' => $carrier,
                                    'api_code' => null,
                                    'carrier_desc' => $rewardsCarrierCode['description'],
                                    'rate' => $baseRate,
                                    'discounted_rate' => $rewardsCarrierCode['rate'],
                                    'delivery_datetime' => null,
                                    'delivery_method' => $rewardsCarrierCode['delivery_method'],
                                    'delivery_term' => $rewardsCarrierCode['delivery_term'],
                                    'is_default' => false
                                ];
                            }
                        } else {
                            $baseRate = \Illuminate\Support\Arr::get($baseRates, $rewardsCarrierCode['base_method'], 0);
                            if ($baseRate > 0) {
                                if(!isset($combinedShipVia[$rewardsCarrierCode['delivery_method']])){
                                    $combinedShipVia[$rewardsCarrierCode['delivery_method']] = [];
                                }

                                $combinedShipVia[$rewardsCarrierCode['delivery_method']][] = [
                                    'carrier_name' => $carrier,
                                    'api_code' => null,
                                    'carrier_desc' => $rewardsCarrierCode['description'],
                                    'rate' => $baseRate,
                                    'discounted_rate' => $rewardsCarrierCode['rate'],
                                    'delivery_datetime' => null,
                                    'delivery_method' => $rewardsCarrierCode['delivery_method'],
                                    'delivery_term' => $rewardsCarrierCode['delivery_term'],
                                    'is_default' => false
                                ];
                            }
                        }
                    }
                }
            }
        }

        $customerCategory = $soldToCustomer->overrideCustomerCategory;
        $category = $customerCategory->cust_category;

        if ($category === 'OPTICIAN' || $category === 'OPTOMETRIST' || $category === 'OPHTHALMOLOGIST' || $category === 'LEASED DOCTOR') {
            if(isset($shipVia['U03'])) {
                $carrier = $shipVia['U03']['carrier_name'];
                if (count($excludedItems) && count($rewardsItems)) {
                    if ($netRewardsOrderTotal >= 150) {
                        if (isset($rewardsBaseRates['U03']) && isset($excludedBaseRates['U03'])) {
                            $baseRate = \Illuminate\Support\Arr::get($rewardsBaseRates, 'U03', 0);
                            $rate = round($baseRate / 4, 2);
                            $baseRate += \Illuminate\Support\Arr::get($excludedBaseRates, 'U03', 0);

                            if (!isset($combinedShipVia['WDF'])) {
                                $combinedShipVia['WDF'] = [];
                            }

                            $combinedShipVia['WDF'][] = [
                                'carrier_name' => $carrier,
                                'api_code' => null,
                                'carrier_desc' => 'Promotional Ground Shipping',
                                'rate' => $baseRate,
                                'discounted_rate' => $rate,
                                'delivery_datetime' => null,
                                'delivery_method' => 'WDF',
                                'delivery_term' => 'D25',
                                'is_default' => false
                            ];
                        }
                    }
                } else if (count($rewardsItems)) {
                    if ($netOrderTotal >= 150) {
                        // If baserates doens't exist, don't offer 1409

                        if (isset($baseRates['U03'])) {
                            $baseRate = \Illuminate\Support\Arr::get($baseRates, 'U03', 0);
                            $rate = round($baseRate / 4, 2);

                            if (!isset($combinedShipVia['WDF'])) {
                                $combinedShipVia['WDF'] = [];
                            }

                            $combinedShipVia['WDF'][] = [
                                'carrier_name' => $carrier,
                                'api_code' => null,
                                'carrier_desc' => 'Promotional Ground Shipping',
                                'rate' => $baseRate,
                                'discounted_rate' => $rate,
                                'delivery_datetime' => null,
                                'delivery_method' => 'WDF',
                                'delivery_term' => 'D25',
                                'is_default' => false
                            ];
                        }

                    }
                }
            }
        }

        $customerCategory = $soldToCustomer->overrideCustomerCategory;
        $category = $customerCategory->cust_category;

        if ($this->activeCountry($shippingAddress) == 'US') { // hacky wrapper if-statement to force promotion to only apply to US customers (per Bob Woyton's email at 3:14pm on 12/0117)
            if(isset($shipVia['U03'])) {
                $baseRate = \Illuminate\Support\Arr::get($shipVia, 'U03', 0);
                // check if order qualifies for shipping discount promotions and add them to shipVia list
                $totalShippingDiscounts = 0;
                $triggeredPromotions = \Illuminate\Support\Arr::get($cartStatus, 'triggeredPromotions');
                foreach ($triggeredPromotions as $triggeredPromotion) {
                    $shippingDiscountActions = $triggeredPromotion->shippingDiscountActions;
                    foreach ($shippingDiscountActions as $shippingDiscountAction) {
                        if ($totalShippingDiscounts < $baseRate) {
                            if ($shippingDiscountAction->is_percent) {
                                $totalShippingDiscounts += round(($baseRate['rate'] * $shippingDiscountAction->amount / 100), 2);
                            } else {
                                $totalShippingDiscounts += $shippingDiscountAction->amount;
                            }
                        }
                    }
                }

                if ($totalShippingDiscounts > 0) {
                    if ($totalShippingDiscounts > $baseRate['rate']) {
                        $totalShippingDiscounts = $baseRate['rate'];
                    }

                    $discTerm = 'D00';
                    if ($totalShippingDiscounts == $baseRate['rate']) {
                        $discTerm = 'F00';
                    }

                    $discMethod = 'BWG';
                    if (!isset($combinedShipVia[$discMethod])) {
                        $combinedShipVia[$discMethod] = [];
                    }

                    $combinedShipVia[$discMethod][] = [
                        'carrier_name' => $baseRate['carrier_name'],
                        'api_code' => null,
                        'carrier_desc' => 'Promotional Ground Shipping',
                        'rate' => $baseRate['rate'],
                        'discounted_rate' => $totalShippingDiscounts,
                        'delivery_datetime' => null,
                        'delivery_method' => $discMethod,
                        'delivery_term' => $discTerm,
                        'is_default' => false
                    ];
                }
            }
        }

        if (count($combinedShipVia) == 0) {
            $combinedShipVia[0] = ['id' => -1, 'text' => trans('hilco::app.unable_to_calculate_shipping_rates')];
        }else{
            if(isset($shipVia[$defaultDeliveryMethodCode])) {

                $possibleDefaults = $combinedShipVia[$defaultDeliveryMethodCode];

                $defaultKey = 0;
                foreach ($possibleDefaults as $key => $possibleDefault) {
                    if ($defaultDeliveryMethodCode == $possibleDefault['delivery_method']) {
                        if (isset($possibleDefault['delivery_term']) && $defaultDeliveryTermCode == $possibleDefault['delivery_term']) {
                            $defaultKey = $key;
                            break;
                        }
                    }
                }

                $combinedShipVia[$defaultDeliveryMethodCode][$defaultKey]['is_default'] = true;
            }
        }

        $finalizedShipVia = [];
        foreach($combinedShipVia as $deliveryOptions){
            $finalizedShipVia = array_merge($finalizedShipVia, $deliveryOptions);
        }

        return $finalizedShipVia;
    }

//  ******************************** RATE SHOP ********************************

    public function hasSentFeedback()
    {
        $user = auth()->user();
        $has = ($user) ? auth()->user()->webFeedbackResponses()->count() > 0 : false;
        return $has;
    }

    public function getShipmentRates(CustomerShippingAddress $customerShippingAddress)
    {
        $cartComposer = App::make('cartSingleton');
        $cartStatus = $cartComposer->getCartStatus();
        $rateType = \Illuminate\Support\Arr::get($this->activeWebSilo(), 'shipment_rate_type', 'api');
        $rates = new Collection;

        if ($rateType == 'api') {
            $carriers = \Illuminate\Support\Arr::get($this->activeWebSilo(), 'carriers', []);
            $package = new Package(\Illuminate\Support\Arr::get($cartStatus, 'totalWeight', 0), \Illuminate\Support\Arr::get($cartStatus, 'totalVolume', 0), $customerShippingAddress->postal_cd, $customerShippingAddress->country);
            foreach ($carriers as $carrier) {

            }
        } else {
            $rates = \Illuminate\Support\Arr::get($this->activeWebSilo(), 'shipmentFlatRates', false);
            krsort($rates);
            $cartValue = 0;
            if ($rateType == 'flatByWeight') $cartValue = \Illuminate\Support\Arr::get($cartStatus, 'totalWeight', 0);
            if ($rateType == 'flatByPrice') $cartValue = \Illuminate\Support\Arr::get($cartStatus, 'discountedTotal', 0);
            foreach ($rates as $threshold => $rate) {
                if ($cartValue >= $threshold) {
                    $rates->push([
                        'rate' => $rate,
                    ]);
                    break;
                }
            }
        }

        return $rates;
    }

    public function getAPIShipmentRates($cartStatus, $customerShippingAddress)
    {

    }

    public function allowBannerCarousel($lpModel)
    {
        if ($this->activeWebSilo()->id == '2') {
            if ($lpModel instanceof WebGroup || $lpModel instanceof WebCategory || $lpModel instanceof WebCollection) {
                return false;
            }
        }
        return true;
    }

    /**
     * Retrieves product models from an array of productType, productId pairs
     *
     * @param $products
     */
    public function retrieveProductModels($products)
    {
        $idsByType = [];
        $sortOrder = [];
        foreach ($products as $index => $product) {
            $type = \Illuminate\Support\Arr::get($product, 'productType');
            $id = \Illuminate\Support\Arr::get($product, 'productId');
            Arr::set($idsByType, "$type.$id", $id);
            Arr::set($sortOrder, $id, $index);
        }

        $map = Relation::morphMap();
        $models = collect();
        foreach ($idsByType as $type => $ids) {
            $type = normalizeProductType($type);
            $className = \Illuminate\Support\Arr::get($map, $type, false);
            if ($className === false) continue;
            $classModels = $className
                ::hasVisibleChildren()
                ->hasVisibleParents()
                ->isVisible()
                ->findMany($ids);
            foreach ($classModels as $model) {
                $models->put(Arr::get($sortOrder, $model['id'], 0), $model);
            }
        }
        $models = $models->sortKeys();
        return $models;
    }

    public function defaultRole()
    {
        $defaultRoleSlug = config('hilco.defaultWebRoleSlug');
        $webRole = WebRole::where('slug', '=', $defaultRoleSlug)->first();

        return $webRole;
    }

    public function catalogsWebLink()
    {
        $activeWebSilo = $this->activeWebSilo();
        $activePlantId = $this->activePlant()->id;
        $catalogsWebLinkId = $activeWebSilo->catalogs_weblink_id;
        $linkJoins = $activeWebSilo->webLinkWebSiloJoins->where('weblink_id', $catalogsWebLinkId)->keyBy('plant_id');
        $validLink = \Illuminate\Support\Arr::get($linkJoins, "$activePlantId", \Illuminate\Support\Arr::get($linkJoins, '-1'));
//        if (\Illuminate\Support\Arr::get($validLink, 'is_visible')) {
//
//        }
        return $validLink;

    }

    function promosHaveCartHeaders($cartStatus)
    {
        $triggeredPromotions = $cartStatus['triggeredPromotions'];
        $almostQualifiedPromotionsInfo = $cartStatus['almostQualifiedPromotionsInfo'];
        if (!is_null($triggeredPromotions)) {
            foreach ($triggeredPromotions as $promotion) {
                if (strlen($promotion->cart_header)) {
                    return true;
                }
            }
        }

        if(!is_null($almostQualifiedPromotionsInfo)){
            foreach($almostQualifiedPromotionsInfo as $key => $almostQualifiedPromoInfo){
                if(isset($almostQualifiedPromoInfo['promotion'])){
                    $promotion = $almostQualifiedPromoInfo['promotion'];
                    if(strlen($promotion->almost_header) > 0){
                        return true;
                    }
                }
            }
        }
        return false;

    }

    function promosHaveCheckoutHeaders($cartStatus)
    {
        $triggeredPromotions = $cartStatus['triggeredPromotions'];
        $almostQualifiedPromotionsInfo = $cartStatus['almostQualifiedPromotionsInfo'];
        if (!is_null($triggeredPromotions)) {
            foreach ($triggeredPromotions as $promotion) {
                if (strlen($promotion->checkout_header)) {
                    return true;
                }
            }
        }

        if(!is_null($almostQualifiedPromotionsInfo)){
            foreach($almostQualifiedPromotionsInfo as $key => $almostQualifiedPromoInfo){
                if(isset($almostQualifiedPromoInfo['promotion'])){
                    $promotion = $almostQualifiedPromoInfo['promotion'];
                    if(strlen($promotion->almost_header) > 0){
                        return true;
                    }
                }
            }
        }
        return false;

    }

    public function shippingErrorMessage()
    {
        if ($this->activeDivision()->division_trimmed == Division::AU_CODE) {
            return trans('messages.please_refer_to_freight_details');
        } else if ($this->activeDivision()->division_trimmed == Division::UK_CODE) {
            return trans('messages.shipping_per_sales_policy');
        } else if ($this->activeDivision()->division_trimmed == Division::DE_CODE) {
            return '';
        } else {
            return trans('messages.shippingError');
        }
    }

    public function defaultLocale()
    {
        return 'us_EN';
    }

    public function defaultLanguage() {
        $webSiloDefaultLanguage = $this->activeWebSilo()->defaultLanguage();
        if (isset($webSiloDefaultLanguage)) {
            return $webSiloDefaultLanguage->language_code;
        } else {
            return config('app.locale');
        }
    }

    public function isProductRestrictedByAddress ($partNumber) {
        $bannedPartStateCount =
            BannedPartsState
                ::where('part_no', $partNumber)
                ->where('state', $this->activeShippingAddress()->state)
                ->where('country', $this->activeShippingAddress()->country)
                ->count()
        ;
        return $bannedPartStateCount > 0;
    }

    public function isProductRestrictedByCustomer($partNumber) {
        $bannedPartCustomerCount =
            BannedPartsCustomer
                ::where('customer_id', $this->activeCustomer()->id)
                ->where('part_no', $partNumber)
                ->count()
        ;
        return $bannedPartCustomerCount > 0;
    }

    public function doesCartHaveBannedProductRestrictions() {
        $bannedPartsByCustomer =
            BannedPartsCustomer
                ::where('customer_id', $this->activeCustomer()->id)
                ->whereIn('part_no', function ($query) {
                    $query
                        ->select('part_number')
                        ->from('WebCarts')
                        ->where('webuser_id', auth()->user()->id)
                        ->where('deleted_at', '=', '0000-00-00 00:00:00')
                    ;
                })
                ->get()
        ;

        $bannedPartsByState =
            BannedPartsState
                ::where('state', $this->activeShippingAddress()->state)
                ->where('country', $this->activeShippingAddress()->country)
                ->whereIn('part_no', function ($query) {
                    $query
                        ->select('part_number')
                        ->from('WebCarts')
                        ->where('webuser_id', auth()->user()->id)
                        ->where('deleted_at', '=', '0000-00-00 00:00:00')
                    ;

                })
                ->get()
        ;

        return
            (isset($bannedPartsByCustomer) && count($bannedPartsByState) > 0) ||
            (isset($bannedPartsByCustomer) && count($bannedPartsByCustomer) > 0)
        ;
    }

    public function cartHasColdShipParts() {
        $coldShipPartsCount =
            WebCart
                ::where('webuser_id', auth()->user()->id)
                ->whereExists(function($query) {
                    return
                        $query
                            ->select(DB::raw('1'))
                            ->from('Parts')
                            ->where('refrig_flag', '=', '1')
                            ->whereRaw('part_no = part_number')
                    ;
                })
                ->count()
        ;
        return $coldShipPartsCount > 0;
    }

    public function cartHasDangerousGoods() {
        $dangerousGoodsCount = WebCart::where('webuser_id', auth()->user()->id)->whereExists(function ($query) {
            return $query->select(DB::raw('1'))->from('Parts')->where('hazard_class', Part::DANGER_CLASS)->whereRaw('part_no = part_number');
        })->count();
        return $dangerousGoodsCount > 0;
    }

    /**
     * @return bool
     */
    public function cartHasRegulatedItems(): bool {
        $regulatedItemsCount = WebCart::where('webuser_id', auth()->user()->id)
            ->whereExists(function ($query) {
                return $query
                    ->select(DB::raw('1'))
                    ->from('Parts')
                    ->where('procurement_group', Part::REGULATED_GROUP)
                    ->whereRaw('Parts.part_no = WebCarts.part_number');
            })->count();
        return $regulatedItemsCount > 0;
    }

    /**
     * @return bool
     */
    public function cartNeedsRegulatedItemApproval(): bool {
        if ($this->cartHasRegulatedItems()) {
            $customer = $this->activeCustomer();
            $shippingAddress = $this->activeShippingAddress();
            $customerPharmacyLicenses = $customer->customerPharmacyLicenses;
            foreach ($customerPharmacyLicenses as $customerPharmacyLicense) {
                if (isset($customerPharmacyLicense->customerPharmacyInfo)) {
                    if (!$customerPharmacyLicense->customerPharmacyInfo->isExpired()) {
                        if ($customerPharmacyLicense->shipto_address_no == $shippingAddress->addr_no) {
                            return false;
                        }
                    }
                }
            }
            return true;
        }
        return false;
    }

//  ******************************** PASSWORDS ********************************
    public function getPasswordValidationMinLength()
    {
        return 8;
    }

    public function getPasswordValidationRegex()
    {
        return '/^(?=.*[a-zA-Z])(?=.*\d).+$/';
    }

    public function getPasswordValidationString()
    {
        $passwordValidationMinLength = $this->getPasswordValidationMinLength();
        $passwordValidationRegex = $this->getPasswordValidationRegex();
        $passwordValidationString = "min:$passwordValidationMinLength|regex:$passwordValidationRegex";
        return $passwordValidationString;
    }

    public function generateRandomPasswordString($length = null, $passwordValidationRegex = null)
    {
        if (!isset($length)) {
            $length = $this->getPasswordValidationMinLength();
        }

        if (!isset($passwordValidationRegex)) {
            $passwordValidationRegex = $this->getPasswordValidationRegex();
        }

        $allowedCharacters = '346789abcdefghkmnpqrtuvwxyzABCDEFGHJKMNPRTUVWXY';
        $allowedCharactersLength = strlen($allowedCharacters);
        $randomPasswordString = '';
        for ($i = 0; $i < $length; $i++) {
            $randomPasswordString .= $allowedCharacters[rand(0, $allowedCharactersLength - 1)];
        }

        if (!preg_match($passwordValidationRegex, $randomPasswordString)) {
            $randomPasswordString = $this->generateRandomPasswordString($length, $passwordValidationRegex);
        }

        return $randomPasswordString;
    }
//  ******************************** PASSWORDS ********************************

//  ***************************** TRANSLATIONS ***************************
    public function availableLanguages() {
        $languages = AvailableLanguage::where('deleted_at', '=', '0000-00-00 00:00:00')->get();
        $languageList = [];
        foreach ($languages as $language){
            $languageList[$language->language_code] = $language->language_name;
        }

        if (config('app.env') !== 'production') {
            $languageList['foo'] = 'foo';
        }
        return $languageList;
    }

    public function getB2BTranslation($bladePath, $translationKey, $placeHolder) {
        // FOO Test Handler
        if (b2b()->activeLanguage() == 'foo') {
            return 'FOO';
        }
        // FOO Test Handler

        return $this->getApplicationTranslation('b2b', $bladePath, $translationKey, $placeHolder);
    }

    /**
     * Gets the ApplicationTranslation record identified by the
     * $application, $bladePage, $translationKey, and current $activeLanguage
     * If a record isn't found for the current $activeLanguage, return the
     * translation text record for English (en)
     * If somehow there isn't an English translation for the intended text,
     * return the value in $placeHolder.
     * @param $application
     * @param $component
     * @param $translationKey
     * @param $placeHolder
     * @return mixed
     */
    public function getApplicationTranslation($application, $component, $translationKey, $placeHolder) {
        $translation = $placeHolder;

        $applicationTranslationKey =
            ApplicationTranslationKey::byApplicationComponentKey($application, $component, $translationKey)->first();

        if ($applicationTranslationKey) {
            $applicationTranslation =
                ApplicationTranslation
                    ::byTranslationKeyLanguage($applicationTranslationKey->id, b2b()->activeLanguage())->first();
            if ($applicationTranslation) {
                $translation = $applicationTranslation->translation;
            } else {
                $defaultTranslation =
                    ApplicationTranslation
                        ::byTranslationKeyLanguage($applicationTranslationKey->id, AvailableLanguage::DEFAULT_LANG_CODE)->first();
                if ($defaultTranslation) {
                    $translation = $defaultTranslation->translation;
                }
            }
        }

        return $translation;
    }

    public function getB2BDataTablesTranslationParameters($bladePath) {
        return $this->getDataTablesTranslationParameters('b2b', $bladePath);
    }

    public function getDataTablesTranslationParameters($application, $component) {
        // FOO Test Handler
        if (b2b()->activeLanguage() == 'foo') {
            return config('datatables.language_foo');
        }
        // FOO Test Handler

        if (!ApplicationTranslationKey::byApplicationComponent($application, $component)->get()->count()) {
            $component = 'datatables.default';
        }

        $translationsArray = config('datatables.language_placeholders');
        foreach ($translationsArray as $languageKey => $languageValue) {
            if (is_array($languageValue)) {
                foreach($languageValue as $subKey => $subValue) {
                    $translationsArray[$languageKey][$subKey] =
                        $this->getApplicationTranslation($application, $component,
                            $languageKey.'.'.$subKey, $translationsArray[$languageKey][$subKey]);
                }
            } else {
                $translationsArray[$languageKey] =
                    $this->getApplicationTranslation($application, $component,
                        $languageKey, $translationsArray[$languageKey]);
            }
        }

        return $translationsArray;
    }
//  **********************************************************************

    public function getLanguages()
    {
        $languages = AvailableLanguage::where('deleted_at', '=', '0000-00-00 00:00:00')->get();
        $languageList = [];
        foreach ($languages as $language){
            $languageList[$language->language_code] = $language->language_name . ' (' . $language->language_code . ')';
        }
        return $languageList;
    }

    public function getApplications(){
        $apps = ApplicationTranslationKey::distinct()->pluck('application')->toArray();
        $appList = [];
        foreach($apps as $app){
            $appList[$app] = strtoupper($app);
        }
        return $appList;
    }

    /**
     * @deprecated I guess refrig parts info is handled by landing page now (see B2BHelper::getRefrigPageSlug())
     * @return bool|\Carbon\Carbon|float|Collection|int|mixed|string|null
     */
    public function getRefrigAssetId() {
        $webAsset = WebAsset::where('asset_key', '=', config('hilco.hilcoRefrigWarningImageKey'))->first();
        if(!empty($webAsset)){
            return $webAsset->id;
        }
    }

    public function getRefrigPageSlug() {
        return config('hilco.hilcoRefrigPageSlug');
    }

    public function getSubscriptionPeriods(){
        $result = [];
        $subscriptionPeriods = SubscriptionPeriod::get();
        foreach ($subscriptionPeriods as $subscriptionPeriod){
            $result[] = ["id"=>$subscriptionPeriod->id, "name"=>trans("subscription.periods.$subscriptionPeriod->period_name")];
        }
        return $result;
    }

    public function getSubscriptionGroups(){
        $subscriptionGroups = SubscriptionGroup::where('createdby_webuser_id', auth()->user()->id)->get();

        $result = [];
        foreach($subscriptionGroups as $subscriptionGroup) {
            $formattedDate = $this->formatDate($subscriptionGroup->next_order_date);

            $result[$subscriptionGroup->int_id] = "$subscriptionGroup->label - Next Shipment on $formattedDate";
        }
        return $result;
    }

    public function miscrositeSubscribeAndSaveEnabled(){
        return $this->activeWebSilo()->allow_subscribe_and_save == 1;
    }

    public function datatableLocalizedURL() {
        //'en_US', 'en_DE', 'de_DE', 'fr_CA', 'fr_FR', 'nl_NL'
        if ($this->activeLanguage() == 'de_DE') {
            return '//cdn.datatables.net/plug-ins/1.10.13/i18n/German.json';
        } else if ($this->activeLanguage() == 'fr_CA' || $this->activeLanguage() == 'fr_FR') {
            return '//cdn.datatables.net/plug-ins/1.10.13/i18n/French.json';
        } else if ($this->activeLanguage() == 'nl_NL') {
            return '//cdn.datatables.net/plug-ins/1.10.13/i18n/Dutch.json';
        } else {
            return '//cdn.datatables.net/plug-ins/1.10.13/i18n/English.json';
        }
    }

    /**
     * helper method for formatting pricing data to send as input param for WebFamiliesController::fetchPrices()
     * @param $webFamilies (some list or collection of WebFamily model objects)
     * @return array [<webfamily id> => [<part number> => <webpart min qty>], ...]
     */
    public function formatFamilyPricingData ($webFamilies): array {
        $familyPricingData = [];
        foreach ($webFamilies as $webFamily) {
            foreach ($webFamily->visibleWebPartsCondensed as $visibleWebPart) {
                $familyPricingData[$webFamily->id][] = [
                    'partNo' => $visibleWebPart->part_number,
                    'qty' => $visibleWebPart->min_quantity
                ];
            }
        }
        return $familyPricingData;
    }

    /**
     * @param $webFamilies
     * @return array
     */
    public function formatFamilyInventoryData ($webFamilies): array {
        $familyInventoryData = [];
        foreach ($webFamilies as $webFamily) {
            foreach ($webFamily->visibleWebPartsCondensed as $visibleWebPart) {
                if ($visibleWebPart->part->inventoryItems->where('part_stat', '!=', \Hilco\Models\InventoryItem::STATUS_DONOTREORDER)->count() == 0) {
                    $familyInventoryData[$webFamily->id] = $visibleWebPart->part_number;
                }
            }
        }
        return $familyInventoryData;
    }

    /**
     * @param Customer|null $customer
     * @param Customer|null $payer
     * @return bool
     */
    public function shouldUseDummyPricing(Customer $customer = null, Customer $payer = null): bool {
        $webSilo = $this->activeWebSilo();
        // Check to see if these overrides should just be skipped and go straight to never showing dummy
        if ($webSilo->never_override_listprice == 1) {
            return false;
        }

        if (is_null($customer)) {
            $customer = $this->activeCustomer();
        }
        $customersegment = $customer->customerSegment;
        $soldto = $customer;
        $soldtoparent = $customersegment->parentCustomer; // could be null
        if (is_null($payer)) {
            $payer = $customersegment->billToCustomer;
        }
        $payerParent = $payer->customerSegment->parentCustomer; // could be null

        $dummyApplies = true;

        //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;
        }

        return $dummyApplies;
    }
}