<?php
namespace Hilco\Pricing;

use Arr;
use DB;
use Hilco\Models\CROBJC;
use Hilco\Models\Customer;
use Hilco\Models\CustomerSegment;
use Hilco\Models\M3DiscountCode;
use Hilco\Models\OGDIPO;
use Hilco\Models\OGDMTX;
use Hilco\Models\OPRICL;
use Hilco\Models\OPRMTX;
use Hilco\Models\Part;
use Hilco\Models\PriceListSchema;

class PriceHelper {
    /**
     * @param $pricelistConnection, "hilco-pricelist-a" or "hilco-pricelist-b" (fetch from PriceListSchemas table)
     * @param Customer $customer, e.g. Customer object
     * @param Part $item, e.g. Part object
     * @param int $quantity
     * @param $customerSegment, e.g. CustomerSegment object, can be null, if populated must be the segment of $customer
     * @return OPRICL|null
     */
    public static function getPriceRecord ($pricelistConnection, Customer $customer, Part $item, int $quantity, $customerSegment = null): ?OPRICL {
        if (is_null($pricelistConnection)) $pricelistConnection = PriceListSchema::getActivePriceListSchema()->getPriceListConnection();

        if (!($customerSegment instanceof CustomerSegment) || $customerSegment->getCustNo() != $customer->getCustNo()) {
            $customerSegment = $customer->customerSegment;
        }

        $objectMappingRecord = CROBJC::on($pricelistConnection)->byPriceListTable($customerSegment->getPriceListTable())->first();
        if (isset($objectMappingRecord)) {
            for ($priorityLevelIndex = 0; $priorityLevelIndex < CROBJC::NUM_PRIORITY_LEVELS; $priorityLevelIndex++) {
                $obvValues = [];
                for ($ruleIndex = 1; $ruleIndex <= CROBJC::NUM_PC_RULES; $ruleIndex++) {
                    $rule = $objectMappingRecord->getRule($priorityLevelIndex, $ruleIndex);
                    switch ($rule) {
                        case "&ITNO": {
                            $obvValues[$ruleIndex] = $item->getPartNo();
                            break;
                        }
                        case "&CUNO": {
                            $obvValues[$ruleIndex] = $customer->getCustNo();
                            break;
                        }
                        case "&PYNO": {
                            $obvValues[$ruleIndex] = $customerSegment->getBillCustNo();
                            break;
                        }
                        case "&DIVI": {
                            $customerDivision = $customerSegment->divisionRelation;
                            $obvValues[$ruleIndex] = $customerDivision->division_trimmed;
                            break;
                        }
                        case "OKCUCL": {
                            $customerCategory = $customer->overrideCustomerCategory;
                            $obvValues[$ruleIndex] = $customerCategory->getCustCategoryDesc();
                            break;
                        }
                        case "&CHAIN": {
                            /*
                             * Couple things to note here:
                             *
                             * 1. As far as we can tell, all current web-customers for Germany and UK have only 1 chain-level.
                             * The German customers that have a second level -- Lexxoo accounts -- don't use the webshop at the moment.
                             * (see email from Christopher Abt (subject "RE: Business Chain usage/pricing D01 Division") on 2022-10-17
                             *
                             * 2. If we did have to account for the Lexxoo customers, we still wouldn't be sure about how
                             * to deal with a chain that has multiple levels. Lexxoo confirms that when determining price
                             * the correct place to start is the lowest level in the chain (for them, chain level 2), but
                             * what happens, for example, if there's no price for the item at that level? Do we check chain
                             * level 1 next? In the data it doesn't seem like the 1st level chain has any pricing, but maybe
                             * that's just coincidental.
                             *
                             * For now, we are assuming that the lowest populated level in the chain is the chain to use,
                             * and if that doesn't have a price then skip it.
                             */
                            $validBusinessChain = $customer->validBusinessChain;
                            if (isset($validBusinessChain)) {
                                $obvValues[$ruleIndex] = $validBusinessChain->getLowestPopulatedChainLink();
                            }
                            break;
                        }
                    }
                }

                if (count($obvValues)) {
                    $priceListMatrixRecords = OPRMTX::findPriceListMatrixRecords(
                        $pricelistConnection,
                        $objectMappingRecord->getPriceListTable(),
                        $priorityLevelIndex,
                        Arr::get($obvValues, '1', ''),
                        Arr::get($obvValues, '2', ''),
                        Arr::get($obvValues, '3', ''),
                        Arr::get($obvValues, '4', ''),
                        Arr::get($obvValues, '5', '')
                    );
                    foreach ($priceListMatrixRecords as $priceListMatrixRecord) {
                        $priceList = $priceListMatrixRecord->getPriceList();
                        $priceListRecord = OPRICL::findPriceRecordForItem($pricelistConnection, $priceList, $customer->getCustNo(), $customer->getCurrency(), $item->getPartNo(), $quantity);
                        if (is_null($priceListRecord)) {
                            $priceListRecord = OPRICL::findPriceRecordForItem($pricelistConnection, $priceList, "", $customer->getCurrency(), $item->getPartNo(), $quantity);
                        }
                        if (isset($priceListRecord)) {
                            return $priceListRecord;
                        } // else continue down the priority levels
                    }
                }

            }
        }
        return null;
    }

    /**
     * @param $pricelistConnection, "hilco-pricelist-a" or "hilco-pricelist-b" (fetch from PriceListSchemas table)
     * @param Customer $customer , e.g. Customer object
     * @param Part $item , e.g. Part object
     * @param $quantity , e.g., 3
     * @param $basePrice , e.g., 1.25, needed for populating the rewards discount info
     * @param array $m3DiscountCodes , e.g., can be empty [], should be set to the equivalent of M3DiscountCode::all()->pluck('discount_type', 'discount_code')->toArray()
     * @param null $customerSegment , e.g. CustomerSegment object, can be null, if populated must be the segment of $customer
     * @return array
     */
    public static function calculateDiscountDataForItem ($pricelistConnection, Customer $customer, Part $item, $quantity, $basePrice, array $m3DiscountCodes = [], $customerSegment = null): array {
        if (is_null($pricelistConnection)) $pricelistConnection = PriceListSchema::getActivePriceListSchema()->getPriceListConnection();

        if (empty($m3DiscountCodes)) {
            $m3DiscountCodes = M3DiscountCode::all()->pluck('discount_type', 'discount_code')->toArray();
        }
        if (!($customerSegment instanceof CustomerSegment) || $customerSegment->getCustNo() != $customer->getCustNo()) {
            $customerSegment = $customer->customerSegment;
        }
        $discountModel = $customerSegment->getDiscountModel();
        $discountBucketRecords = OGDIPO::on($pricelistConnection)->byDiscModel($discountModel)->orderBy('DIPO', 'ASC')->get();
        $discountData = [];
        foreach ($discountBucketRecords as $discountBucketRecord) {
            $dipo = $discountBucketRecord->getBucketNo();
            $tx08 = $discountBucketRecord->getBucketName();
            $discountInfo = [
                "bucketNo" => $dipo,
                "DIP$dipo" => '',
                "DIA$dipo" => '',
                "DIC$dipo" => '1',
                "TX8$dipo" => $tx08,
            ];

            if (isset($m3DiscountCodes[$tx08])) {
                switch ($m3DiscountCodes[$tx08]) {
                    case "Other": {
                        $discType = 'customer';
                        break;
                    }
                    case 'Rewards':{
                        $discType = 'rewards';
                        break;
                    }
                    case 'One-Off':{
                        $discType = 'promotion';
                        break;
                    }
                }
            } else {
                $discType = $discountBucketRecord->getBucketName();
            }

            for ($priorityLevel = 1; $priorityLevel <= OGDIPO::NUM_PRIORITY_LEVELS; $priorityLevel++) {
                $obvValues = [];
                $adjustedLevel = ($priorityLevel == 10) ? 0 : $priorityLevel;
                for ($ruleIndex = 1; $ruleIndex <= OGDIPO::NUM_PC_RULES; $ruleIndex++) {
                    $rule = $discountBucketRecord->getRule($adjustedLevel, $ruleIndex);
                    switch ($rule) {
                        case "&CUCD": {
                            $obvValues[$ruleIndex] = $customer->getCurrency();
                            break;
                        }
                        case "&CUNO": {
                            $obvValues[$ruleIndex] = $customer->getCustNo();
                            break;
                        }
                        case "&FACI": {
                            $obvValues[$ruleIndex] = $customerSegment->getFacilityCode();;
                            break;
                        }
                        case "&ITNO": {
                            $obvValues[$ruleIndex] = $item->getPartNo();
                            break;
                        }
                        case "&PYNO": {
                            $obvValues[$ruleIndex] = $customerSegment->getBillCustNo();
                            break;
                        }
                        case "MMGRP1": {
                            $obvValues[$ruleIndex] = $item->getSearchGroup1();
                            break;
                        }
                        case "MMITCL": {
                            $obvValues[$ruleIndex] = $item->productFamily->productCategory->getProductCategoryNo();
                            break;
                        }
                        case "MMITGR": {
                            $obvValues[$ruleIndex] = $item->productFamily->getProductFamilyNo();
                            break;
                        }
                        case "OKCUCL": {
                            $obvValues[$ruleIndex] = $customer->overrideCustomerCategory->getCustCategory();
                            break;
                        }
                        case "OKDIGC": {
                            $obvValues[$ruleIndex] = $customerSegment->getDiscountGroup();
                            break;
                        }
                        case "&CHAIN": {
                            /*
                             * Couple things to note here:
                             *
                             * 1. As far as we can tell, all current web-customers for Germany and UK have only 1 chain-level.
                             * The German customers that have a second level -- Lexxoo accounts -- don't use the webshop at the moment.
                             * (see email from Christopher Abt (subject "RE: Business Chain usage/pricing D01 Division") on 2022-10-17
                             *
                             * 2. If we did have to account for the Lexxoo customers, we still wouldn't be sure about how
                             * to deal with a chain that has multiple levels. Lexxoo confirms that when determining price
                             * the correct place to start is the lowest level in the chain (for them, chain level 2), but
                             * what happens, for example, if there's no price for the item at that level? Do we check chain
                             * level 1 next? In the data it doesn't seem like the 1st level chain has any pricing, but maybe
                             * that's just coincidental.
                             *
                             * For now, we are assuming that the lowest populated level in the chain is the chain to use,
                             * and if that doesn't have a price then skip it.
                             */
                            $validBusinessChain = $customer->validBusinessChain;
                            if (isset($validBusinessChain)) {
                                $obvValues[$ruleIndex] = $validBusinessChain->getLowestPopulatedChainLink();
                            }
                            break;
                        }
                    }
                }
                if (!empty($obvValues)) {
                    $discountRecord = OGDMTX::findDiscountRecord(
                        $pricelistConnection,
                        $discountModel,
                        $discountBucketRecord->getBucketNo(),
                        $adjustedLevel,
                        Arr::get($obvValues, '1', ''),
                        Arr::get($obvValues, '2', ''),
                        Arr::get($obvValues, '3', ''),
                        Arr::get($obvValues, '4', ''),
                        Arr::get($obvValues, '5', ''),
                        $quantity
                    );
                    if (isset($discountRecord)) {
                        $discountInfo["DIP$dipo"] = $discountRecord->getDiscountPercentage();
                        $discountInfo["DIA$dipo"] = "" . round($basePrice * $quantity * $discountRecord->getDiscountPercentage() / 100, 2, PHP_ROUND_HALF_UP);
                    }
                }
            }

            if ($discType == 'rewards') {
                $price = round($basePrice * $quantity, 2);
                $discountData[$discType] = self::populateRewardsDiscInfo($item, $price, $discountInfo, $customer);
            } else {
                $discountData[$discType] = $discountInfo;
            }
        }
        return self::determineBestDiscount($discountData);
    }

    /**
     * Helper function for when you only have the part number
     * @param $partNumber
     * @param $regularExtPrice
     * @param $discInfo
     * @param Customer $customer
     * @return array
     */
    public static function populateRewardsDiscInfoWithPartNumber ($partNumber, $regularExtPrice, $discInfo, $customer): array
    {
        $item = Part::with(['rewardsPartExclusion', 'rewardsFamilyExclusion'])
            ->with('productFamily.productCategory.productCategoryGroup')
            ->wherePartNo($partNumber)->whereNull('deleted_at')->first();
        return self::populateRewardsDiscInfo($item, $regularExtPrice, $discInfo, $customer);
    }

    /**
     * @param Part $part
     * @param $regularExtPrice
     * @param $discInfo
     * @param Customer $customer
     * @return array
     */
    public static function populateRewardsDiscInfo (Part $part, $regularExtPrice, $discInfo, $customer): array {
        $discPercentage = 0;
        if (!is_null($part)) {
            if (!$part->isRewardsExcluded()) {
                $discPercentage = rewards()->discountForPart($part, $customer);
                $discAmount = round($regularExtPrice * ($discPercentage / 100), 2);
                foreach ($discInfo as $key => $value) {
                    if (strpos($key, "DIP") !== false) {
                        $discInfo[$key] = $discPercentage;
                    } else if (strpos($key, "DIA") !== false) {
                        $discInfo[$key] = $discAmount;
                    }
                }
            } else {
                foreach ($discInfo as $key => $value) {
                    if (strpos($key, "DIP") !== false) {
                        $discInfo[$key] = 0;
                    } else if (strpos($key, "DIA") !== false) {
                        $discInfo[$key] = 0;
                    }
                }
            }
        }

        return $discInfo;
    }

    /**
     * This is primarily relevant to B&S because they're the only ones who set up pricing to have multiple buckets with
     * applicable discounts, and they need the system to select the "best" discount to actually use and "zero-out" the rest.
     * Technically the SGGC column on OGDIPO is what identifies this (SGGC = "2"), but at the moment every record in OGDIPO
     * is set to "2". It just doesn't matter to AUS/UK/US discounts because those should all have a single discount.
     * @param array $discountData
     * @return array
     */
    private static function determineBestDiscount (array $discountData): array {
        $bestDiscKey = '';
        $bestDiscPerc = 0.0;
        foreach ($discountData as $discType => $discountDatum) {
            $bucket = $discountDatum['bucketNo'];
            $dip = "DIP$bucket";
            if ($bestDiscPerc < $discountDatum[$dip]) {
                $bestDiscPerc = $discountDatum[$dip];
                $bestDiscKey = $discType;
            }
        }
        if (!empty($bestDiscKey) && $bestDiscPerc > 0) {
            foreach (array_keys($discountData) as $discType) {
                if ($discType != $bestDiscKey) {
                    $bucket = $discountData[$discType]['bucketNo'];
                    $discountData[$discType]["DIP$bucket"] = "";
                }
            }
        }
        return $discountData;
    }
}