<?php

namespace Hilco\Inventory;

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

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)
        // Note: 2023-04 Hilco changed their kits process for their new warehouse, no longer need us to calc. this way
//        $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);
//            }
//        }

        $regularItems =
            Part::whereIn('part_no', $itemNumbers)
//                ->where('item_type', '!=', 'KIT')
                ->with('inventoryItems')
                ->get();
        foreach ($regularItems as $regularItem) {
            $partNo = $regularItem->getPartNo();
            $warehouse = $itemsWarehouses[$partNo];
            try {
                $AV01 = self::getNetAvailable($inventoryConnection, $regularItem, $warehouse);
                $AV01 = max($AV01, 0); // if we've received any value then we know the item is stockable, treat any negative values as 0 out-of-stock
            } catch (MITBALNotFoundException $exception) {
                Log::warning($exception->getMessage(), $exception->getTrace());
                $AV01 = -1;
            }
            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];
            if (!isset($itemWarehouseBalances[$warehouseToUse]) || !isset($itemWarehouseBalances[$warehouseToUse][$itemNo])) {
                continue;
            }
            $highStatus = $itemWarehouseBalances[$warehouseToUse][$itemNo]['status'];
            $highStock = $itemWarehouseBalances[$warehouseToUse][$itemNo]['stock'];
            foreach ($warehouseCodes as $wh) {
                if ($wh != $warehouseToUse && isset($itemWarehouseBalances[$wh]) && isset($itemWarehouseBalances[$wh][$itemNo])) {
                    $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
     * @throws MITBALNotFoundException
     */
    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) {
            // Note: 2023-04 Hilco changed the kits process for the new warehouse, no longer need us to calc. this way,
            // leaving this logic here in case they ever need us to resurrect it...
            // 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 {
                throw new MITBALNotFoundException("Inventory record not found for item ".$item->getPartNo()." in warehouse $warehouseCode.");
            }
        }

        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,
        ];
    }
}