<?php

namespace Hilco\Inventory;

use Hilco\Models\InventoryItem;
use Hilco\Models\InventorySchema;
use Hilco\Models\MITBAL;
use Hilco\Models\Part;

class InventoryHelper {
    /**
     * @param $inventoryConnection
     * @param array $itemsWarehouses , expected array of [<item number> => <warehouse code>] pairs
     * @param bool $forCST , default false
     * @return array
     */
    public static function getItemWarehouseBalances ($inventoryConnection, array $itemsWarehouses, bool $forCST = false): array {
        if (is_null($inventoryConnection)) $inventoryConnection = InventorySchema::getActiveInventorySchema()->getInventoryConnection();

        $warehouseBalances = [];

        $itemNumbers = array_keys($itemsWarehouses);

        // Kitted Items (bill of materials)
        $kittedItems =
            Part::whereIn('part_no', $itemNumbers)
                ->where('item_type', 'KIT')
                ->with('partBOMs')
                ->get();
        foreach ($kittedItems as $kittedItem) {
            $partNo = $kittedItem->getPartNo();
            $warehouse = $itemsWarehouses[$partNo];
            $calculatedAV01 = self::getNetAvailable($inventoryConnection, $kittedItem, $warehouse, true);
            if ($forCST) {
                $warehouseBalances[] = self::buildWhsBalCSTObj($partNo, $warehouse, $calculatedAV01);
            } else {
                $warehouseBalances[$partNo] = self::buildStockStatusB2BObj($kittedItem, $warehouse, $calculatedAV01);
            }
        }

        // Regular Items == Non-Kitted Items
        $regularItems =
            Part::whereIn('part_no', $itemNumbers)
                ->where('item_type', '!=', 'KIT')
                ->with('inventoryItems')
                ->get();
        foreach ($regularItems as $regularItem) {
            $partNo = $regularItem->getPartNo();
            $warehouse = $itemsWarehouses[$partNo];
            $AV01 = self::getNetAvailable($inventoryConnection, $regularItem, $warehouse);
            if ($forCST) {
                $warehouseBalances[] = self::buildWhsBalCSTObj($partNo, $warehouse, $AV01);
            } else {
                $warehouseBalances[$partNo] = self::buildStockStatusB2BObj($regularItem, $warehouse, $AV01);
            }
        }

        return $warehouseBalances;
    }

    /**
     * @param array $itemNumbers , expected array of M3 item numbers
     * @param array $warehouseCodes , expected array of M3 warehouse codes
     * @param array $itemWarehouseBalances , expected array like [<warehouse code> => [<item number> => ['stock' => <AV01>, 'status' => <Part stock status value>>, 'stockable' => <true|false>], ...], ...]
     * @return array
     */
    public static function getHighestStockStatuses (array $itemNumbers, array $warehouseCodes, array $itemWarehouseBalances): array {
        $stockInfoReturn = [];
        $statusMap = Part::getStockStatusMap();
        foreach ($itemNumbers as $itemNo) {
            $warehouseToUse = $warehouseCodes[0];
            $highStatus = $itemWarehouseBalances[$warehouseToUse][$itemNo]['status'];
            $highStock = $itemWarehouseBalances[$warehouseToUse][$itemNo]['stock'];
            foreach ($warehouseCodes as $wh) {
                if ($wh != $warehouseToUse) {
                    $whStatus = $itemWarehouseBalances[$wh][$itemNo]['status'];
                    $whStock = $itemWarehouseBalances[$wh][$itemNo]['stock'];
                    if (($statusMap[$highStatus] < $statusMap[$whStatus]) ||
                        ($statusMap[$highStatus] == $statusMap[$whStatus] && $highStock < $whStock)) {
                        $highStatus = $whStatus;
                        $highStock = $whStock;
                        $warehouseToUse = $wh;
                    }
                }
            }
            $stockInfoReturn[$itemNo] = $itemWarehouseBalances[$warehouseToUse][$itemNo];
        }
        return $stockInfoReturn;
    }

    /**
     * @param $inventoryConnection
     * @param Part $item
     * @param string $warehouseCode
     * @param bool $isKit
     * @return int
     */
    public static function getNetAvailable ($inventoryConnection, Part $item, string $warehouseCode, bool $isKit = false): int {
        if (is_null($inventoryConnection)) $inventoryConnection = InventorySchema::getActiveInventorySchema()->getInventoryConnection();

        $AV01 = 0;

        if ($isKit) {
            // Kit component items
            $partBOMs = $item->partBOMs;
            // total possible kits per current qty of component item
            $numKitsPerComp = [];
            foreach ($partBOMs as $partBOM) {
                $componentAV01 = self::getNetAvailable($inventoryConnection, $partBOM->compPart, $warehouseCode);
                if ($componentAV01 >= 0) {
                    $numKitsPerComp[] = floor($componentAV01 / $partBOM->getQuantity());
                }
            }
            if (count($numKitsPerComp) > 0) {
                sort($numKitsPerComp);
                $AV01 = $numKitsPerComp[0];
            }
        } else {
            $balanceRecord = MITBAL::findBalanceRecord($inventoryConnection, $warehouseCode, $item->getPartNo());
            if (isset($balanceRecord)) {
                $AV01 = $balanceRecord->getNetAvailableQuantity();
            } else {
                $AV01 = -1;
            }
        }

        return $AV01;
    }

    /**
     * @param $AV01
     * @return string
     */
    private static function getStatusForCST ($AV01): string {
        if ($AV01 > 0) {
            return "INSTOCK";
        } else if ($AV01 == 0) {
            return "BACKORDERED";
        } else {
            return "OBSOLETE";
        }
    }

    /**
     * @param $partNumber
     * @param $m3ID
     * @param $AV01
     * @return array[]
     */
    private static function buildWhsBalCSTObj ($partNumber, $m3ID, $AV01): array {
        return [
            'whsBal' => [
                'partNumber' => $partNumber,
                'm3ID' => $m3ID,
                'AV01' => max($AV01, 0),
                'status' => self::getStatusForCST($AV01),
            ],
        ];
    }

    /**
     * @param Part $item
     * @param $warehouseCode
     * @param $AV01
     * @return string
     */
    private static function getStatusForB2B (Part $item, $warehouseCode, $AV01): string {
        if ($AV01 >= 0) {
            $inventoryItem = $item->inventoryItems->filter(function ($inventoryItem) use ($warehouseCode) {
                return $inventoryItem->plant === $warehouseCode;
            })->first();
            if (isset($inventoryItem)) {
                if ($inventoryItem->part_stat == InventoryItem::STATUS_DONOTREORDER) {
                    return Part::LOW_STOCK;
                } else if ($AV01 > 0) {
                    return Part::IN_STOCK;
                } else {
                    return Part::OUT_OF_STOCK;
                }
            }
        }
        return Part::UNKNOWN;
    }

    /**
     * @param Part $item
     * @param $warehouseCode
     * @param $AV01
     * @return array
     */
    private static function buildStockStatusB2BObj (Part $item, $warehouseCode, $AV01): array {
        return [
            'stock' => max($AV01, 0),
            'stockable' => $AV01 >= 0,
            'status' => self::getStatusForB2B($item, $warehouseCode, $AV01),
            'warehouse' => $warehouseCode,
        ];
    }
}