<?php

namespace Hilco\Models;

use DB;
use Auth;
use HilcoB2B\M3Request;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use \Illuminate\Support\Arr;
use Log;

/**
 * Class WebPart
 * @package Hilco\Models
 * @property int $id
 * @property string $name
 * @property string $safe_name
 * @property string $override_name
 * @property-read Part $part
 * @property-read WebFamily $webFamily
 * @property-read WebPart_WebFamily[] $webPartWebFamilies
 * @property-read WebFamily[] $webFamilies
 * @property-read WebAttribute[] $webAttributes
 * @property-read WebFamily[] $relatedParent
 * @method static Builder latest()
 * @property string $part_number
 * @property integer $part_id
 * @property integer $webfamily_id
 * @property boolean $is_visible
 * @property integer $commit_sequence
 * @property boolean $is_family_image
 * @property integer $min_quantity
 * @property string $out_of_stock_label
 * @property string $in_stock_label
 * @property integer $quantity_step
 * @property string $quantity_presets
 * @property string $keywords
 * @property boolean $has_subscribe_and_save
 * @property string $date_created
 * @property string $date_modified
 * @property string $date_uploaded
 * @property string $deleted_at
 * @property-write mixed $web_part_attributes
 * @property-read mixed $part_name
 * @property-read mixed $family_name
 * @property-read mixed $context
 * @property-read mixed $default_context
 * @property-read mixed $hierarchy_paths
 * @mixin \Eloquent
 * @method static WebPart firstOrNew(array $attributes)
 */
class WebPart extends WebModel {
    protected $table = "WebParts";
    protected $fillable = [
        'min_quantity', 'quantity_step', 'quantity_presets',
        'name', 'webAttributes', 'webfamily_id', 'part_id', 'part_number',
        'is_visible', 'is_family_image',
        'in_stock_label','low_stock_label', 'out_of_stock_label',
        'has_subscribe_and_save', 'delayFor360', 'disable360'
    ];
    protected $casts = [
        ['is_visible' => 'boolean' , 'is_family_image' => 'boolean']
    ];

    public static $perEnvironment = true;

    public function indexOnly() {
        if (!Arr::get($this, 'is_visible', false)) return false;
        if (!Arr::get($this, 'webFamily.is_visible', false)) return false;

        return true;
    }

// ***************************************SOLR**************************************************************************
    public function getSolrRecord($webSilos) {
        $record['id']                       = $this->id;
        $record['part_id']                  = $this->part_id;
        $record['name']                     = $this->name;
        $record['part_number']              = $this->part_number;
        $record['nodash_part_number'] = str_replace('-', '', $this->part_number);
        $record['legacy_part_no'] = $this->part->legacy_part_no;
        $record['nodash_legacy_part_no'] = str_replace('-', '', $this->part->legacy_part_no);

        // Add language localized versions of translatable columns
        foreach ($this->translations as $webModelTranslation) {
            $columnName = $webModelTranslation->column_name;
            $language = $webModelTranslation->language;
            $value = $webModelTranslation->translation;
            $transKey = $columnName . '_' . $language; // e.g., 'name_en', 'name_fr'
            $record[$transKey] = $value;
        }

        // This field should be deprecated following the release of support for WebPart_WebFamily
        $record['webFamily_slug'] = !is_null($this->webFamily) ? $this->webFamily->slug : null;

        // Add "searchable" fields with non-alphanumeric characters stripped out of the values
        // /[^A-Za-z0-9\x{00C0}-\x{00FF}\x{1E9E}]/u matches all non-alphanumerics, the 00C0-00FF range of Unicode Latin-1 Supplement characters, and 1E9E (ß) from Unicode Latin Extended Additional
        foreach ($record as $key => $value) {
            $record[$key."_searchable"] = preg_replace("/[^A-Za-z0-9\x{00C0}-\x{00FF}\x{1E9E}]/u", '', $value);
        }

        $record['is_rx']            = $this->part->is_rx;
        $record['b2b_web_visible']  = $this->is_solr_visible;

        $record['validPlants'] = [];
        if (!is_null($this->part->inventoryItems)) {
            $listedPlants = [];
            foreach ($this->part->inventoryItems as $inventoryItem) {
                if ($inventoryItem->part_stat != InventoryItem::STATUS_OBSOLETE &&
                    (is_null($inventoryItem->web_flag) || in_array($inventoryItem->web_flag, config('hilco.inventoryItemWebFlags')))) {
                    $listedPlants[] = $inventoryItem->plant;
                }
            }
            $record['validPlants'] = $listedPlants;
        }

        // Populate list of microsites for which this item should be valid and visible
        $record['webSiloIds'] = [];
        if ($webSilos === null) {
            $webSilos = WebSilo::visible()->get();
        }
        foreach ($webSilos as $webSilo) {
            if ($this->visibleInSilo($webSilo)) {
                $record['webSiloIds'][] = $webSilo->id;
            }
        }

        $record['webCollectionIds'] = [];
        if (!empty($this->webFamilies)) {
            foreach ($this->webFamilies as $webFamily) {
                $record['webCollectionIds'] = array_unique(
                    array_merge(
                        $record['webCollectionIds'],
                        $webFamily->webCollections->pluck('id')->toArray()
                    )
                );
            }
        }

        $record['is_rx'] = $this->part->is_rx;
        $record['b2b_web_visible'] = $this->is_solr_visible;

        // Populate list of assortments the part is a member of (this might be needed anymore..)
        $record['assortments'] = [];
        foreach ($this->part->assortments as $assortment) {
            $record['assortments'][] = $assortment->assortment_name;
        }
        if (count($record['assortments']) == 0) {
            $record['assortments'][] = 'NO_ASSORTMENT';
        }

        $record['autoship'] = (bool)$this->has_subscribe_and_save;
        return $record;
    }

    public function solrTable()
    {
        return $this->hasMany(WebPart_Solr::class, 'webPart_id', 'id');
    }

    public function getSolrClassAttribute() {
        return WebPart_Solr::class;
    }

    public function getSolrIDFieldAttribute() {
        return 'webPart_id';
    }

    public function getIsSolrVisibleAttribute() {
        return static::solrVisible()->where('id', $this->id)->count() > 0;
    }

    public function scopeSolrVisible(Builder $query) {
        return
            $query
                ->isVisible()
                ->hasSolrVisibleParents()
                ->hasSolrVisibleChildren()
            ;
    }

    public function scopeHasSolrVisibleParents(Builder $query) {
        return
            $query
                ->whereHas('webFamilies', function ($query) {
                    return $query->isVisible()->hasSolrVisibleParents();
                })
            ;
    }

    public function scopeHasSolrVisibleChildren(Builder $query) {
        return $query
            ->whereHas('part', function ($query) {
                return $query->hasSolrVisibleChildren();
            })
            ;
    }
// *********************************************************************************************************************

// ***************************************RELATIONSHIPS*****************************************************************
    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function part() {
		return $this->belongsTo(Part::class, 'part_id', 'id');
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function webFamily() {
        return $this->webFamilyInHierarchy(b2b()->activeWebHierarchy()->id);
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function webPartWebFamilies() {
        return $this->hasMany(WebPart_WebFamily::class, 'webpart_id', 'id');
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function webFamilies() {
        return $this->belongsToMany(WebFamily::class, 'WebPart_WebFamily', 'webpart_id', 'webfamily_id')
            ->wherePivot('deleted_at', '=', '0000-00-00 00:00:00');
    }

    /**
     * @param $webHierarchyId
     * @return BelongsTo
     */
    public function webFamilyInHierarchy ($webHierarchyId) {
        $webPartWebFamily = $this->webPartWebFamilies()->inHierarchy($webHierarchyId)->first();
        if (isset($webPartWebFamily)) {
            return $webPartWebFamily->webFamily();
        } else {
            $webFamily = new WebFamily();
            $webPartWebFamily = new WebPart_WebFamily();
            return new BelongsTo($webFamily->newQuery(), $webPartWebFamily, 'webfamily_id', 'id', 'webFamily');
        }
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     * @deprecated There should be no reason for this relationship to exist, LegacyPart isn't even a valid model. Remove this once we determine there are no more usages
     */
    public function legacyPart(){
	    return $this->hasOne(LegacyPart::class, 'part_id', 'id');
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function webAttributes() {
        return $this->belongsToMany(WebAttribute::class, 'WebAttribute_WebPart', 'webpart_id', 'webattribute_id')
            ->wherePivot('deleted_at', '=', '0000-00-00 00:00:00')
            ->withPivot(['id','attribute_value']);
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\MorphToMany
     */
    public function relatedParent() {
        return $this->morphToMany(WebFamily::class, 'related', 'WebFamily_Related', 'related_id', 'webfamily_id')->wherePivot('deleted_at', '=', '0000-00-00 00:00:00')->withPivot(['related_category']);
    }

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

    public function hierarchyPaths() {
        $paths = ['webPart' => $this];
        if ($this->webFamily) $paths = $this->webFamily->hierarchyPaths($paths);
        return $paths;
    }

    public function partAsset() {
        return $this->hasOne(WebAsset::class, 'name', 'part_number')->where('asset_category', 'part')->orderBy('date_created', 'DESC');
    }

    public function webAttributeWebParts() {
        return $this->hasMany(WebAttribute_WebPart::class, 'webpart_id', 'id');
    }

    public function webPartProductFeatures() {
        return $this->hasMany(WebPartProductFeature::class, 'webpart_id', 'id');
    }
// *********************************************************************************************************************

// ***************************************GETTERS/SETTERS***************************************************************
    public function webAttributeValue($webAttributeId) {
        $webAttributeWebPart = $this->webAttributeWebParts->where('webattribute_id', $webAttributeId)->first();
        if (isset($webAttributeWebPart)) return $webAttributeWebPart->attribute_value; // WebAttribute_WebPart::getAttributeValueAttribute() method needs to get invoked
//        if (!$this->relationLoaded('webAttributes')) $this->load('webAttributes');
//        $webAttributeValue = $this->webAttributes->find($webAttributeId);
//        if (!is_null($webAttributeValue)) return $webAttributeValue->pivot->attribute_value;
        return null;
    }

    public function setWebAttributeValue($webAttributeId, $value) {
        $this->webAttributes()->sync([$webAttributeId => ['attribute_value' => $value]], false);
    }

    public function setWebAttributesAttribute($attributes) {
        $this->save();
        foreach ($attributes as $webAttributeId => $attributeValue) {
            $this->setWebAttributeValue($webAttributeId, $attributeValue);
        }
    }

    public function getPartNameAttribute() {
        $part = $this->part;
        if (is_null($part)) return null;
        return $part->part_desc;
    }

    public function getFamilyNameAttribute() {
        $family = $this->webFamily;
        if (is_null($family)) return null;
        return $family->name;
    }

    public function getNameAttribute() {
        if (config('hilco.ignoreActiveWebSilo')) {
            return $this->getTranslation('name', AvailableLanguage::DEFAULT_LANG_CODE, $this->attributes['name']);
        } else {
            return $this->getTranslation('name');
        }
    }

    public function getDisplayNameForUACAttribute() {
        return $this->attributes['name'] . ' #' . $this->attributes['part_number'];
    }

    public function getListPriceAttribute() {
        return $this->part->list_price;
    }

    public function getCustomerPriceAttribute() {
        return $this->part->customer_price;
    }

    public function getCustomerPrice($quantity = 1) {
        return $this->part->getCustomerPrice($quantity);
    }

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

    public function getImageLink($width = null, $height = null, $avoidCache = false) {
        return $this->primaryImage($width ? $width : false, $height ? $height : false);
    }

    public function getImageUrl($width = false, $height = false) {
        return HilcoAssets::part($this, $width, $height);
    }

    public function getIsRXAttribute() {
        $part = $this->part;
        if (is_null($part)) return null;
        return (!is_null($part->pharmacyFamilyExclusion) || !is_null($part->pharmacyPartExclusion));
    }

    public function getHasValidInventoryAttribute() {
        $status = 'none';
        $hasAll = true;
        if ($this->part) {
            foreach ($this->part->inventoryItems as $inventoryItem) {
                if ($inventoryItem->is_valid) {
                    $status = 'some';
                } else {
                    $hasAll = false;
                }
            }
        }

        if ($hasAll) $status = 'all';
        return $status;
    }

    public function getHierarchyPathsAttribute() {
        $isVisible = true;
        if (!$this->is_visible) $isVisible = trans('hilco::app.manually_hidden');
        if (is_null($this->part)) $isVisible = trans('hilco::app.not_in_point_man');
        if (empty($this->webFamilies)) $isVisible = trans('hilco::app.not_in_a_family');
        if ($this->has_valid_inventory == 'none') $isVisible = trans('hilco::app.no_valid_inventory');

        $paths = [];
        if (!empty($this->webFamilies)) {
            foreach ($this->webFamilies as $webFamily) {
                foreach($webFamily->webCollections as $webCollection) {
                    foreach ($webCollection->webCategories as $webCategory) {
                        foreach ($webCategory->webGroups as $webGroup) {
                            foreach ($webGroup->webHierarchies as $webHierarchy) {
                                if (!$webFamily->is_visible)
                                    $isVisible = trans('hilco::app.parent_family_not_visible');
                                if (!$webCollection->is_visible)
                                    $isVisible = trans('hilco::app.parent_collection_not_visible');
                                if (!$webCategory->is_visible)
                                    $isVisible = trans('hilco::app.parent_category_not_visible');
                                if (!$webGroup->is_visible) $isVisible = trans('hilco::app.parent_group_not_visible');
                                $paths[] = [
                                    'webHierarchy' => $webHierarchy,
                                    'webGroup' => $webGroup,
                                    'webCategory' => $webCategory,
                                    'webCollection' => $webCollection,
                                    'webFamily' => $webFamily,
                                    'isVisible' => $isVisible,
                                ];
                            }
                        }
                    }
                }
            }
        }
        return $paths;
    }

    public function setWebfamilyIdAttribute($value) {
        if (!$value || $value < 1) $this->attributes['webfamily_id'] = null;
        else $this->attributes['webfamily_id'] = $value;
    }

    public function getInStockAttribute() {
        return $this->part->in_stock;
    }

    public function getInStockLabelAttribute() {
        $modelValue = Arr::get($this->attributes, 'in_stock_label');
        return strlen($modelValue) ? $modelValue : trans('app.in_stock_status_label' );
    }

    public function getLowStockLabelAttribute() {
        $modelValue = Arr::get($this->attributes, 'low_stock_label');
        return strlen($modelValue) ? $modelValue : trans('app.low_stock_status_label');
    }

    public function getOutOfStockLabelAttribute() {
        $modelValue = Arr::get($this->attributes, 'out_of_stock_label');
        return strlen($modelValue) ? $modelValue : trans('app.out_of_stock_status_label');
    }

    public function getStockStatusAttribute() {
        $atpData = [$this->part_number];
        $atps = [];
        try {
            $atps = WebPart::fetchAllItemsStockInfo($atpData);
        }catch(Exception $e){
            Log::error("Exception caught while fetching stock info: " . $e->getMessage());
            return Part::UNKNOWN;
        }

        return $atps[0]['status'];
    }

    public function getPartTaxability($linePrice, $shippingAddress = null){
        $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 Part::UNKNOWN;
        }else{
            $client = $guzzle->client;
        }

        if ($shippingAddress === null) $shippingAddress = b2b()->activeShippingAddress();
        $queryAttributes = [
            'requestFrom' => 'b2b',
            'streetAddress1' => $shippingAddress->addr_1,
            'streetAddress2' => $shippingAddress->addr_2,
            'streetAddress3' => $shippingAddress->addr_3,
            'streetAddress4' => $shippingAddress->addr_4,
            'streetAddress5' => $shippingAddress->addr_5,
            'city' => $shippingAddress->city,
            'mainDivision' => $shippingAddress->state,
            'postalCode' => $shippingAddress->postal_cd,
            'country' => $shippingAddress->country,
            'part_number' => $this->part->part_no,
            'productClass' => $this->part->vertexid,
            'unit_price' => $linePrice
        ];

        $response = $client->get('v4/' . __FUNCTION__, ['query' => http_build_query($queryAttributes), 'headers' => ['Accept' => 'application/json']]);
        $taxResults = $response->getBody()->getContents();
        $taxResults = json_decode($taxResults, true);

        return $taxResults['taxes'];
    }

    public function getStockLabel($status){
        switch ($status) {
            case Part::IN_STOCK:
                return $this->in_stock_label;
            case Part::LOW_STOCK:
                return $this->low_stock_label;
            case Part::OUT_OF_STOCK:
                return $this->out_of_stock_label;
            case Part::DELAYED:
                return trans('app.delayed_stock_status_label');
            case Part::UNKNOWN:
                return trans('app.unknown_stock_status_label');
        }
    }

    public function getStockLabelAttribute() {
        $status = $this->stock_status;
        switch ($status) {
            case Part::IN_STOCK:
                return $this->in_stock_label;
            case Part::OUT_OF_STOCK:
                return $this->out_of_stock_label;
            case Part::DELAYED:
                return "Ships in 3-5 days";
            case Part::UNKNOWN:
                return ucfirst(Part::UNKNOWN);
        }
    }

    public function getPrimaryImage() {
        $webAsset = $this->assetsByType('primary')->first();
        if (is_null($webAsset)) {
            $webAsset = $this->partAsset;
            if (is_null($webAsset) && isset($this->part)) {
                $webAsset = $this->part->legacyPartAsset;
            }
        }

        return $webAsset;
    }

    public function getIsSiloPartAttribute() {
        foreach (Arr::get($this, 'part.webSilos', []) as $partWebSilo) {
            if ($partWebSilo->id == b2b()->activeWebSilo()->id) return true;
        }
        return false;
    }

    public function getRequireApprovalAttribute() {
//        foreach (Arr::get($this, 'part.webSilos', []) as $partWebSilo) {
//            if ($partWebSilo->id == b2b()->activeWebSilo()->id) {
//                if (Arr::get($partWebSilo, 'pivot.require_approval', false)) return true;
//            }
//        }
//        return false;
        return $this->part->require_approval;
    }

    public function getIsWebVisibleAttribute() {
        return static::webVisible()->where('id', $this->id)->count() > 0;
    }

    public function getMinQuantityAttribute() {
        if (is_null($this->attributes['min_quantity'])) {
            return 1;
        } else {
            return $this->attributes['min_quantity'];
        }
    }
// *********************************************************************************************************************

// ***************************************SCOPES************************************************************************
    public function scopeJoinDiscount($query){
        $customerId = Auth::user()->customer_id;
        return $query->select(['WebParts.*',DB::raw('SUM(CustomerDiscounts.disc_val) as disc_val')])->leftJoin('CustomerDiscounts', function($query)use($customerId){
            return $query->on('CustomerDiscounts.part_id','=','WebParts.id')
                ->orOn('CustomerDiscounts.productfamily_id','=','WebParts.webfamily_id')
                ->where('CustomerDiscounts.customer_id','=',$customerId);
        })->groupBy('id');
    }

	public function scopeIsFamilyImage($query){
		$query->whereHas('webPartWebFamilies', function ($query) {
		    return $query->where('is_family_image', 1);
        })->visible()->whereHas('part', function ($query) {
            return $query->whereHas('inventoryItems', function ($query) {
                return $query->isValid();
            });
        });
	}

	public function scopePriceList($query, $priceList, $currency, $quantityLevel = 1) {
		return $query
			->leftJoin('PriceLists', 'PriceLists.part_no', '=', 'WebParts.part_number')
			->where('price_list', $priceList)
			->where('currency', $currency)
			->where('quantity_level', '>=', $quantityLevel)
			->select('WebParts.*', 'PriceLists.price')
		;
	}

	public function scopePriceRange($query, $min, $max) {
		return $query->whereHas('part', function($part) use ($min, $max){
			$part->whereBetween('list_price', [$min, $max]);
		});
	}

	public function scopeManagedBy($query, $manager) {
		return $query->whereHas('part', function ($query) use ($manager) {
			return $query->where('pf_prod_mgr', $manager);
		});
	}

	public function scopeInFamily($query, $webFamilyId) {
		return $query->whereHas('webFamilies', function ($query) use ($webFamilyId) {
			return $query->where('webfamily_id', $webFamilyId);
		});
	}

    public function scopeInCollection($query, $webCollectionId) {
        return $query->whereHas('webFamilies', function ($query) use ($webCollectionId) {
            return $query->inCollection($webCollectionId);
        });
    }

    public function scopeInCategory($query, $webCategoryId) {
        return $query->whereHas('webFamilies', function ($query) use ($webCategoryId) {
            return $query->inCategory($webCategoryId);
        });
    }

	public function scopeInGroup($query, $webGroupId) {
		return $query->whereHas('webFamilies', function ($query) use ($webGroupId) {
			return $query->inGroup($webGroupId);
		});
	}

	public function scopeHasVisibleChildren(Builder $query, $activeWebSilo = false, $activeCountry = false, $activePlant = false) {
	    return $query
            ->whereHas('part', function ($query) use ($activeWebSilo, $activeCountry, $activePlant) {
                return $query->visibleToActiveUser($activeWebSilo, $activeCountry, $activePlant);
            })
        ;
    }

    public function scopeHasVisibleParents(Builder $query, $activeWebSilo = false) {
        return $query
            ->whereHas('webFamilies', function ($query) use ($activeWebSilo) {
                return $query->hasVisibleParents($activeWebSilo);
            })
            ;
    }

    public function scopeIsVisible(Builder $query) {
        return $query->where('WebParts.is_visible', '1');
    }

	public function scopeWebVisible(Builder $query) {
	    return $query
            ->isVisible()
            ->hasVisibleParents()
            ->hasVisibleChildren()
        ;
    }

    public function scopeWebSiloApproved(Builder $query) {
        return $query
            ->whereHas('part', function ($partQuery) {
                return $partQuery->webSiloApproved();
            })
        ;
    }

	public function scopeVisible(Builder $query, $webSiloId = false) {
	    if ($webSiloId === false) {
	        $webSiloId = b2b()->activeWebSilo()->id;
        }
		return $query
			->where('WebParts.is_visible', '1')
			->whereHas('part', function ($query) use ($webSiloId) {

                if (!b2b()->activeWebSilo()->is_default) {
                    $query->whereHas('webSilos', function ($query) {
                        return $query->where('websilo_id', '=', b2b()->activeWebSilo()->id);
                    });
                }

                if (!b2b()->activeWebSilo()->allow_private_label) {
//                    $query->doesntHave('assortments');
                    $query->where('private_lbl', '0');
                }

//				return $query->whereHas('inventoryItems', function ($query) use ($webSiloId) {
//                    $activeCountry = b2b()->activeCountry();
//                    if ($activeCountry == 'US') $query->inPlant('PLAINVILLE');
//                    return $query->inSilo($webSiloId);
//				});
			})
		;
	}

    public function scopeForLegacyPartNumber(Builder $query, $legacyPartNumber) {
        return $query
            ->whereHas('part', function ($query) use ($legacyPartNumber){
                return $query->forLegacyPartNumber($legacyPartNumber);
            });
    }
// *********************************************************************************************************************

    /**
     * @param $webSilo -- this appears to accept either an object of type Hilco\Models\WebSilo or an integer
     * representing an id value of a WebSilos row (this functionality should honestly probably be deprecated)
     * @param bool $processDownChain
     * @param bool $processUpChain
     * @return bool
     */
    public function visibleInSilo($webSilo, $processDownChain = true, $processUpChain = true) {
        // Don't bother checking anything else if this WebPart isn't even linked to a real Part
        if (!isset($this->part)) {
            return false;
        }

        $inSilo = true;
        $isFamilyVisible = !$processUpChain;

        // This should probably be deprecated, see comment in method phpdoc
        if (!$webSilo instanceof WebSilo) $webSilo = WebSilo::find($webSilo);

        if($processDownChain && isset($webSilo)){
            if ($webSilo->limit_parts) {
                $found = false;
                foreach ($this->part->webSilos as $partWebSilo) {
                    if ($partWebSilo->id == $webSilo->id) {
                        $found = true;
                        break;
                    }
                }
                if (!$found) {
                    $inSilo = false;
                }
            }

            if (!$webSilo->allow_private_label && $this->part->private_lbl > 0) {
                $inSilo = false;
            }

            $inventoryItems = $this->part->inventoryItems;
            if (count($inventoryItems)) {
                $atLeastOneValidInventory = false;
                foreach ($inventoryItems as $inventoryItem) {
                    if ($inventoryItem->part_stat != InventoryItem::STATUS_OBSOLETE &&
                        (is_null($inventoryItem->web_flag) || in_array($inventoryItem->web_flag, config('hilco.inventoryItemWebFlags')))) {
                        $atLeastOneValidInventory = true;
                        break;
                    }
                }
                if (!$atLeastOneValidInventory) {
                    $inSilo = false;
                }
            } else {
                $inSilo = false;
            }
        }

        if ($processUpChain) {
            foreach ($this->webFamilies as $webFamily) {
                if ($webFamily->visibleInSilo($webSilo, false, true)) {
                $isFamilyVisible = true;
                    break;
                }
            }
        }

        return ($inSilo && $isFamilyVisible);
    }

    public static function fetchAllItemsStockInfo($partNumbers, $warehouse = null, $customer = null, $webUser = null) {
	    if ($customer == null) {
	        $customer = b2b()->activeCustomer();
        }

        if ($webUser == null) {
            $webUser = auth()->user();
        }

        if (isset($webUser) && BannedApiUser::byWebUserId($webUser->id)->count() > 0) {
            return [];
        }
        
        $stockInfoReturn = [];
        if ($webUser) {
            $guzzleClient = null;
            $guzzleM3Request = new M3Request();
            if (empty($guzzleM3Request->client)) {
                Log::error(
                    'Exception Caught while performing ' . __FUNCTION__ .
                    ': Guzzle Http Client does not exist. Base URI may be missing!');
                return Part::UNKNOWN;
            } else {
                $guzzleClient = $guzzleM3Request->client;
            }

            if (is_null($warehouse)) {
                $warehouse = $customer->activeSegment->def_ship_from;
            }

            $warehouses = Plant::inventoryClusterLogicPlants($warehouse);

            $stockStatusesPerWarehouse = [];
            foreach ($warehouses as $warehouseToCheck) {
                $stockStatusesPerWarehouse[$warehouseToCheck] = [];

                // package up item and warehouse information to be sent to API
                // it's organized by part number for some reason, I'm not gonna change it right now. -- ntaylor 20210611
                $stockInfoRequestData = [];
                foreach ($partNumbers as $partNumber) {
                    $stockInfoRequestData[$partNumber] = [
                        'item_number' => $partNumber,
                        'warehouse' => $warehouseToCheck
                    ];
                }

                // Make GetItmWhsBal calls in chunks of 10
                $chunks = array_chunk($stockInfoRequestData, 10, true);
                $getStockInfoResponseObj['atps'] = []; // note: ATP is outdated term, this is getItemWarehouseBalance
                foreach ($chunks as $chunk) {
                    /*
                     * Expecting a chunk response that resembles:
                     * [
                     *  <part number> => [
                     *      apiVersion" => "v4",
                     *      status" => "success",
                     *      command" => "getItemWarehouseBalance",
                     *      totalTime" => 731,
                     *      details" => [
                     *          Program" => "MMS200MI",
                     *          Transaction" => "GetItmWhsBal",
                     *          Metadata" => null,
                     *          MIRecord" => [
                     *              0 => [
                     *                  NameValue => [
                     *                      0 => [....],
                     *                      1 => [
                     *                          Name => "AV01",
                     *                          Value => "<allocatable net quantity>"
                     *                      ],
                     *                      2 => [.....],
                     *                      3 => [.....],
                     *                      ....
                     *                  ],
                     *                  "RowIndex" => 0
                     *              ]
                     *          ]
                     *      ]
                     *  ],
                     *  <part number> => [....],
                     *  ....
                     * ]
                     *
                     * NOTE 1: AV01 appearing in index 1 in the NameValue array above is just an example
                     * NOTE 2: A kitted item (i.e., a part with item_type KIT and PartBOM rows) will have a details array
                     * that only has AV01:
                     * details => [
                     *      MIRecord => [
                     *          0 => [
                     *              "NameValue" => array:1 [
                     *                  0 => [
                     *                      Name => "AV01",
                     *                      Value => "<total # of possible kits that can be made based on the AV01 values of all its component parts>"
                     *                  ]
                     *              ]
                     *          ]
                     *      ]
                     * ]
                     */
                    $chunkResponse = json_decode(
                        $guzzleClient
                            ->post(
                                'v4/getItemWarehouseBalance',
                                [
                                    'form_params' => [
                                        'requestFrom' => 'b2b',
                                        'webuser' => $webUser->id,
                                        'customer_number' => $customer->cust_no,
                                        'itemsATPRequestInfo' => $chunk
                                    ],
                                    'headers' => [
                                        'Accept' => 'application/json'
                                    ]
                                ]
                            )
                            ->getBody()
                            ->getContents(),
                        true)['atps']
                    ;
                    //not doing array_merge here because we need to preserve our keys
                    $getStockInfoResponseObj['atps'] = $getStockInfoResponseObj['atps'] + $chunkResponse;
                }

                // Process all responses and store them in $stockStatusesPerWarehouse
                foreach ($getStockInfoResponseObj['atps'] as $partNo => $stockInfoData) {
                    if (isset($stockInfoData['details'])) {
                        $details = $stockInfoData['details'];
                        if (Arr::has($details, 'MIRecord') && Arr::has($details['MIRecord'][0], 'NameValue')) {

                            $nameValuePairs = $details['MIRecord'][0]['NameValue'];
                            foreach ($nameValuePairs as $nameValuePair) {
                                if ($nameValuePair['Name'] == 'AV01') {
                                    $numStock = trim($nameValuePair['Value'], ' ');
                                    $stockStatusesPerWarehouse[$warehouseToCheck][$partNo]['stock'] = !empty($numStock) ? round($numStock, 0) : 0;
                                    $stockStatusesPerWarehouse[$warehouseToCheck][$partNo]['stockable'] = true; // the fact that we got any warehouse response at all == stockable

                                    $part = Part::wherePartNo($partNo)->first();
                                    $inventoryItem = $part->inventoryItems()->inPlant($warehouseToCheck)->first();

                                    if (!empty($inventoryItem)) {
                                        if ($inventoryItem->part_stat == 'DoNotReorder') {
                                            $stockStatusesPerWarehouse[$warehouseToCheck][$partNo]['status'] = Part::LOW_STOCK;
                                        } else if ($stockStatusesPerWarehouse[$warehouseToCheck][$partNo]['stock'] > 0) {
                                            $stockStatusesPerWarehouse[$warehouseToCheck][$partNo]['status'] = Part::IN_STOCK;
                                        } else {
                                            $stockStatusesPerWarehouse[$warehouseToCheck][$partNo]['status'] = Part::OUT_OF_STOCK;
                                        }
                                    } else {
                                        $stockStatusesPerWarehouse[$warehouseToCheck][$partNo]['status'] = Part::UNKNOWN;
                                    }
                                }
                            }
                        } else {
                            if (Arr::has($details, 'Message')) {
                                Log::debug(__FUNCTION__ . " received Message " . $details['Message'] . " for item $partNo.");
                                $stockStatusesPerWarehouse[$warehouseToCheck][$partNo] = [
                                    'status' => Part::UNKNOWN,
                                    'stock' => 0,
                                    'stockable' => false
                                ];
                            }
                        }

                    } else {
                        Log::debug(__FUNCTION__ . " received empty or nonexistent 'details' field from API for item $partNo.");
                        $stockStatusesPerWarehouse[$warehouseToCheck][$partNo] = [
                            'status' => Part::UNKNOWN,
                            'stock' => 0,
                            'stockable' => false
                        ];
                    }
                }
            }

            if (count($warehouses) == 1) {
                $stockInfoReturn = $stockStatusesPerWarehouse[$warehouse];
            } else {
                $statusMap = Part::getStockStatusMap();
                foreach ($partNumbers as $partNumber) {
                    $warehouseToUse = $warehouses[0];
                    $highStatus = $stockStatusesPerWarehouse[$warehouseToUse][$partNumber]['status'];
                    $highStock = $stockStatusesPerWarehouse[$warehouseToUse][$partNumber]['stock'];
                    foreach ($warehouses as $wh) {
                        if ($wh != $warehouseToUse) {
                            $whStatus = $stockStatusesPerWarehouse[$wh][$partNumber]['status'];
                            $whStock = $stockStatusesPerWarehouse[$wh][$partNumber]['stock'];
                            if (($statusMap[$highStatus] < $statusMap[$whStatus]) ||
                                ($statusMap[$highStatus] == $statusMap[$whStatus] && $highStock < $whStock)) {
                                $highStatus = $whStatus;
                                $highStock = $whStock;
                                $warehouseToUse = $wh;
                                break;
                            }
                        }
                    }
                    $stockInfoReturn[$partNumber] = $stockStatusesPerWarehouse[$warehouseToUse][$partNumber];
                }
            }
        }

        return $stockInfoReturn;
    }

    public function getPrimaryImageSeoFilename($langCode = false) {
        $assetable = $this->getPrimaryAssetable();
        if (is_null($assetable)) {
            if ($langCode) {
                return $this->getTranslation('name', $langCode);
            } else {
                return $this->name;
            }
        }

        $seoFilename = $assetable->getTranslation('seo_filename');
        if (!strlen($seoFilename)) {
            if ($langCode) {
                return $this->getTranslation('name', $langCode);
            } else {
                return $this->name;
            }
        }

        return $seoFilename;
    }

    public function primaryImageWithSEO($width = false, $height = false) {
        $asset = $this->getPrimaryImage();
        return WebAsset::compileUrlForKey($this->getPrimaryImageWithSeoKey(), $width, $height);
    }

    public function getPrimaryImageWithSeoKey($langCode = false) {
        $seoDescription = str_replace(' ', '-', preg_replace("/[^a-zA-Z0-9- ]/", '', $this->getPrimaryImageSeoFilename($langCode)));
        return 'pf/' . $seoDescription . '_' . $this->part_number . '_product-primary';
    }

    public function getPrimaryImageAltText() {
        $assetable = $this->getPrimaryAssetable();
        if (is_null($assetable)) return $this->name;

        $altText = $assetable->getTranslation('alt_text');
        if (!strlen($altText)) return $this->name;

        return $altText;
//
//        $webAsset = $this->getPrimaryImage();
//
//        if (!isset($webAsset) || !isset($webAsset->pivot)) {
//            return $this->name;
//        }
//
//        $overriddenAltText = $webAsset->pivot->alt_text;
//
//        if ($overriddenAltText == null || $overriddenAltText == '') {
//            return $this->name;
//        } else {
//            return $overriddenAltText;
//        }
    }

    /*
     * Made this function only to find out that there was a simpler way to solve the problem with was designed to solve.
     * Necessary to ensure quick order and multi line quick order enforces Web Silo restrictions, but probably going to
     *  want to rotate that functionality for scope visibile functions.
     */
    public function isWebSiloAccessible($webSilo = null){
        if(is_null($webSilo)){
            $webSilo = b2b()->activeWebSilo();
        }
        if(!($webSilo->limit_parts)){
            return true;
        }
        $webSiloParts = $webSilo->getParts();
        $id = $this->part->id;
        $validWebPart = $webSiloParts->filter(function($item) use ($id){
                return $item->part_id === $id;
            })->first();
        if(!is_null($validWebPart)){
            return true;
        }
        return false;
    }

    public function getQuantityPresetsArrayAttribute() {
	    $presets = $this->quantity_presets;
	    if (strlen($presets) < 1) return [];

	    $array = explode(',', $presets);
	    return array_combine($array, $array);
    }

    public function getIsSubscriptionEligibleAttribute() {
        return false !== b2b()->activeCustomer() && b2b()->miscrositeSubscribeAndSaveEnabled() && $this->has_subscribe_and_save;
    }

    public function getIsSubscribedAttribute() {
        $customerID = b2b()->activeCustomer()->id;

        $subscriptionItems = SubscriptionItem::where('part_id', $this->part_id)->whereExists(function ($query) use ($customerID) {
            $query->select(DB::raw(1))
                ->from('SubscriptionGroups')
                ->whereColumn('SubscriptionItems.subscriptiongroup_id', 'SubscriptionGroups.id')
                ->where('soldto_customer_id', $customerID)
                ->where('deleted_at', 0);
        })->get();

        return $subscriptionItems->count() > 0;

    }

    public function getNextSubscriptionDateAttribute() {
        $customerID = b2b()->activeCustomer()->id;

        $subscriptionItems = SubscriptionItem::where('part_id', $this->part_id)->whereExists(function ($query) use ($customerID) {
            $query->select(DB::raw(1))
                ->from('SubscriptionGroups')
                ->whereColumn('SubscriptionItems.subscriptiongroup_id', 'SubscriptionGroups.id')
                ->where('soldto_customer_id', $customerID)
                ->where('deleted_at', 0);
        })->get();

        $nextSubscriptionDate = false;

        foreach ($subscriptionItems as $subscriptionItem) {
            if ($nextSubscriptionDate === false) {
                $nextSubscriptionDate = $subscriptionItem->subscriptionGroup->next_order_date;
            } else {
                $nextSubscriptionDate = min($nextSubscriptionDate, $subscriptionItem->subscriptionGroup->next_order_date);
            }
        }

        return $nextSubscriptionDate;
    }

    public function slugUrl() {
        return route('part.slug', [$this->id]);
    }

    /**
     * WebParts table does NOT have a 'slug' column, this is just to make certain polymorphic actions with products easier
     * @return string
     */
    public function getSlugAttribute() {
        return $this->part_number;
    }

    /**
     * @param $familyId
     * @param array $notIn
     * @deprecated I don't think this is exactly relevant anymore now that WebPart_WebFamily is a thing
     */
    public static function clearIsFamilyImage($familyId, $notIn=[]){
        self::where('webfamily_id', $familyId)->where('is_family_image',1)->whereNotIn('id',$notIn)->update(['is_family_image'=>0]);
    }

    use HasAssets, HasCommitSequence, HasModelTranslations;
}