<?php
namespace App\Bsp\Settlement;

class CompareWithImportedService
{

	/**
	 * The comparison threshold
	 * @var float
	 */
	protected $comparisonThreshold = 0.0001;

	/**
	 * Collected messages. filled in by the comparer
	 * @var \App\MessagesCollector
	 */
	protected $messages = null;

	/**
	 * Set the message container
	 * @param \App\MessagesCollector $messageContainer
	 */
	public function setMessagesCollector(?\App\MessagesCollector $messageContainer = null)
	{
		$this->messages = $messageContainer;
	}

	/**
	 * Get the message container
	 * @return \App\MessagesCollector $messageContainer
	 */
	public function getMessagesCollector()
	{
		if ($this->messages===null) {
			$this->messages = new \App\MessagesCollector();
		}

		return $this->messages;
	}

	public function setComparisonThreshold($threshold)
	{
		$this->comparisonThreshold = $threshold;
		return $this;
	}

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

	/**
	 * Compare the bspRecord with the importedService
	 * @param \Entity\BspRecord $bspRecord
	 * @param \Entity\ImportedService $importedService
	 * @param array $bspRecordExtraData
	 * @param array $importedServiceExtraData
	 * @return \App\Bsp\Settlement\CompareWithImportedServiceResults
	 */
	public function compare(?\Entity\BspRecord $bspRecord, ?\Entity\ImportedService $importedService, array $bspRecordExtraData, array $importedServiceExtraData)
	{
		$ret = $this->createResult();

		$ret = $this->populateFromBspRecord($ret, $bspRecord, $bspRecordExtraData);
		$ret = $this->populateFromImportedService($ret, $importedService, $importedServiceExtraData);
		$ret->populateDifferences();

		if ($ret->isVoid->bspRow && $ret->isVoid->importedService) {
			// all voids are equal(their prices are 0 in BSP)
			foreach($ret->getMembersWithDifferences() as $member) {
				$member->details = [];
			}
		}
		// else normal comparing, non-conjunction tickets

		return $ret;
	}

	/**
	 * Build the result object. Its split like this so that an extending child would be able to easily use another type of result
	 * @return \App\Bsp\Settlement\CompareWithImportedServiceResults
	 */
	protected function createResult()
	{
		return new CompareWithImportedServiceResults();
	}

	/**
	 * Populate the return from the BSP data
	 * @param CompareWithImportedServiceResults $ret
	 * @param \Entity\BspRecord $bspRecord
	 * @param array $bspRecordExtraData
	 * @return \App\Bsp\Settlement\CompareWithImportedServiceResults
	 */
	protected function populateFromBspRecord(CompareWithImportedServiceResults $ret, \Entity\BspRecord $bspRecord, array $bspRecordExtraData)
	{
		if ($bspRecord->recordExists()) {
			$ret->ticketNumber->bspRow = $bspRecord->data->ticketNumber;

			$ret->ticketDate->bspRow = $bspRecord->data->ticketDate;

			$ret->isRefund->bspRow = $bspRecord->data->extraData->isRefund;

			$ret->isVoid->bspRow = $bspRecord->data->extraData->isVoid;

			$ret->isAdm->bspRow = $bspRecord->data->extraData->isAdm;

			$ret->isIssue->bspRow =
				$bspRecord->data->extraData->isIssue
				|| $bspRecord->data->extraData->isReissue
				|| ($bspRecord->data->priceBalancePayable->amount && !($bspRecord->data->extraData->isRefund || $bspRecord->data->extraData->isVoid || $bspRecord->data->extraData->isAdm));

			$ret->supplierCode3->bspRow = $bspRecord->data->supplierCode3;

			$data = $bspRecord->data;

			$ret->isCc->bspRow = $data->isPaidWith('cc');

			$ret->priceServiceCash->bspRow = $data->priceSupplierCash->amount;

			$ret->priceServiceCc->bspRow = $data->priceSupplierCc->amount;

			$ret->priceTaxCash->bspRow = $data->priceTaxCash->amount;

			$ret->priceTaxCc->bspRow = $data->priceTaxCc->amount;

			$ret->priceBalancePayable->bspRow = $data->priceBalancePayable->amount;

			if ($data->isPaidWith('cc')) {
				$ret->priceCommissionCc->bspRow = 		$data->priceCommission->amount;
				$ret->priceCommissionVATCc->bspRow = 	$data->priceCommissionVat->amount;
			}
			else {
				$ret->priceCommissionCash->bspRow = 	$data->priceCommission->amount;
				$ret->priceCommissionVATCash->bspRow = 	$data->priceCommissionVat->amount;
			}

		}

		return $ret;
	}


	/**
	 * Populate the return from the ImportedOrders data
	 * @param CompareWithImportedServiceResults $ret
	 * @param \Entity\ImportedService $importedService
	 * @param array $importedServiceExtraData
	 * @return \App\Bsp\Settlement\CompareWithImportedServiceResults
	 */
	protected function populateFromImportedService(CompareWithImportedServiceResults $ret, \Entity\ImportedService $importedService, array $importedServiceExtraData)
	{
		if ($importedService->recordExists()) {
			$offer = $importedService->object->chosenOffer;
			if ($offer) {
				$ret->ticketNumber->importedService = $offer->confirmationNumber;

				$ret->isRefund->importedService = $importedServiceExtraData['isRefund'];

				$ret->isVoid->importedService = ($importedService->systemStatus=='cancelled');

				$ret->isIssue->importedService = ($offer->confirmationNumber && !($ret->isVoid->importedService || $ret->isRefund->importedService));

				// supplierCode3
				$suppCodes = [];
				if ($offer->supplier and $offer->supplier->codes) {
					$suppCodes = $offer->supplier->codes;
				}
				$ret->supplierCode3->importedService = $suppCodes['tktCode'];
				if (!$ret->supplierCode3->importedService) {
					// tktCode wasn't found in IO, so match & load it from TINA DB
					if ($offer->supplier) {
						$searchResults = $offer->supplier->getPopulatedMatchObject($importedService->idSystem)->search();

						if ($searchResults) {
							$ret->supplierCode3->importedService = \Entity\Supplier::select($searchResults->getFirstMatchId())->codes['tktCode'];
						}
						// else, no supplier defined?
					}
					// else, no supplier defined?
				}

				// isCc
				$isCc = in_array($offer->formOfPayment, ['CC', 'GOV', ]);
				$ret->isCc->importedService = $isCc;

				if ($importedServiceExtraData['isRefund']) {
					// we're looking at a RFND, we need to revert some of the values
					$ret = $this->distribuiteImportedServiceAmountsRefund($ret, $importedService, $importedServiceExtraData, $isCc);
				}
				else {
					// we're looking at a ISSUE/REISSUE operation
					$ret->ticketDate->importedService = $offer->date;
					$ret = $this->distribuiteImportedServiceAmountsTicketing($isCc, $offer->commissionable, $ret, $importedService, $offer);
				}

				\Engine\Plugins\Manager::getInstance()->executeEvent('App-Bsp-Settlement-CompareWithImportedService-populateFromImportedService:after', [
					'this' =>                   $this,
					'ret' =>                    $ret,
					'importedService' =>        $importedService,
					'offer' =>                  $offer,
					'isCc' =>                   $isCc,
					'importedServiceExtraData' => $importedServiceExtraData,
				]);

			}

		}

		return $ret;
	}

	/**
	 * Distribuite imported service amounts
	 * @param bool mixed $isCc
	 * @param bool mixed $isCommissionable
	 * @param CompareWithImportedServiceResults $ret
	 * @param \Entity\ImportedService $importedService
	 * @param \App\ImportedOrder\Offer $offer
	 * @return CompareWithImportedServiceResults
	 */
	protected function distribuiteImportedServiceAmountsTicketing($isCc, $isCommissionable, \App\Bsp\Settlement\CompareWithImportedServiceResults $ret, \Entity\ImportedService $importedService, \App\ImportedOrder\Offer $offer)
	{
		$offerExtraInfo = (array)$offer->extraInfo;

		// Get commission value based on configuration
		$commission = $offer->commissionTotal;
		if ((string)\App\Bsp\Config::getInstance()->get("commissionHandling")=='useInitialCommission') {
			$commission = (($offerExtraInfo['originalPriceArr']['commission'] ?? $offer->commissionTotal));
		}

		// Get service price and tax values, properly handling commissionable flag
		if ($isCommissionable) {
			$priceService = ($offerExtraInfo['originalPriceArr']['supplier'] ?? ($offer->servicePriceTotal - $commission));
		}
		else {
			$priceService = ($offerExtraInfo['originalPriceArr']['supplier'] ?? $offer->servicePriceTotal);
		}

		$priceTax = (($offerExtraInfo['originalPriceArr']['tax'] ?? (float)$offer->taxTotal));

		$isCcAndCash = in_array(strtolower($offer->formOfPayment?:''), ['cc+cash', 'cash+cc', ]);
		// fill in CASH+CC members
		if ($isCcAndCash && $offer->servicePriceCurrency && $offer->creditCardPaymentCurrency===$offer->servicePriceCurrency && $offer->creditCardPaymentCurrency===$offer->taxCurrency) {
			// distribuitor is used to distribuite price and tax between CC and CASH
			$distribuitor = new \ModulesAbstract\PluginReports\Report\Model\Builder\Helpers\PricesDistribuitor();
			// For CC+CASH payment type
			if ($isCommissionable) {
				$ret->priceServiceCc->importedService = 	$priceService + $commission;
				$ret->priceTaxCc->importedService = 		$priceTax;
				$ret->priceCommissionCc->importedService = 	$commission;
			}
			else {
				$ret->priceServiceCc->importedService = 	$priceService;
				$ret->priceTaxCc->importedService = 		$priceTax;
				$ret->priceCommissionCc->importedService = 	$commission;
			}

			if ($isCommissionable) {
				$initialAmounts = [
					'priceService' => 	$priceService + $commission,
					'priceTax' => 		$priceTax,
				];
			}
			else {
				$initialAmounts = [
					'priceService' => 	$priceService,
					'priceTax' => 		$priceTax,
				];
			}

			try {
				$finalAmounts = $distribuitor->substractAmountOverComponents($offer->creditCardPaymentValue, $initialAmounts, [ 'priceService', 'priceTax', ], \ModulesAbstract\PluginReports\Report\Model\Builder\Helpers\PricesDistribuitor::METHOD_FIRST);
				$groupedChanges = $distribuitor->getTrackedChangesGroupedByKey();
			} catch (\Exception\Validator\InvalidValue $ex) {
				// if we can't distribute the amount, we assume the full amount goes to service price
				$finalAmounts = [
					'priceService' => $offer->creditCardPaymentValue,
					'priceTax' => 0,
				];
				$groupedChanges = [
					'priceService' => 0,
					'priceTax' => 0,
				];
			}

			$ret->priceServiceCash->importedService =  $finalAmounts['priceService'];
			$ret->priceTaxCash->importedService = 	   $finalAmounts['priceTax'];

			$ret->priceServiceCc->importedService =    $groupedChanges['priceService'];
			$ret->priceTaxCc->importedService = 	   $groupedChanges['priceTax'];

			$ret->priceBalancePayable->importedService = $finalAmounts['priceService'] + $finalAmounts['priceTax'];
		}
		elseif ($isCc) {	// For CC payment type
			if ($isCommissionable) {	// Q: how is it possible that $isCommissionable is true for CC payments?
				$ret->priceServiceCc->importedService = 	$priceService + $commission;
				$ret->priceTaxCc->importedService = 		$priceTax;
				$ret->priceCommissionCc->importedService = 	$commission;
			}
			else {
				$ret->priceServiceCc->importedService = 	$priceService;
				$ret->priceTaxCc->importedService = 		$priceTax;
				$ret->priceCommissionCc->importedService = 	$commission;
			}

			// For CC payments, balance payable is typically just the commission (negative)
			$ret->priceBalancePayable->importedService = -1 * $commission;
		}
		else {
			// For CASH, INVOICE, etc payment types
			if ($isCommissionable) {
				$ret->priceServiceCash->importedService = 	 $priceService + $commission;
				$ret->priceTaxCash->importedService = 		 $priceTax;
				$ret->priceCommissionCash->importedService = $commission;

				$ret->priceBalancePayable->importedService = $priceService + $priceTax;
			}
			else {
				$ret->priceServiceCash->importedService = 	 $priceService;
				$ret->priceTaxCash->importedService = 		 $priceTax;
				$ret->priceCommissionCash->importedService = $commission;

				$ret->priceBalancePayable->importedService = $priceService + $priceTax;
			}
		}

		// Set balance payable to 0 for cancelled services
		if ($importedService->systemStatus=='cancelled') {
			$ret->priceBalancePayable->importedService = 0.0;
		}

		return $ret;
	}

	/**
	 * Handles refund logic for imported services
	 * @param CompareWithImportedServiceResults $ret
	 * @param \Entity\ImportedService $importedService
	 * @param array $importedServiceExtraData
	 * @param bool $isCc
	 * @return CompareWithImportedServiceResults
	 */
	protected function distribuiteImportedServiceAmountsRefund(
		CompareWithImportedServiceResults $ret,
		\Entity\ImportedService $importedService,
		array $importedServiceExtraData,
		$isCc
	) {
		$refund = $importedService->object->refundDetails;
		$ret->ticketDate->importedService = $refund->date;
		// TODO: commissionable is not used in here?
		if ($refund) {
			$rebuiltRefund = \App\Utils\BaseParser\Utils\RebuildImportedOrderRefund::getInstance()->rebuildOriginalRefund($importedService);
			if ($isCc) {
				// fill in CC members, same as for cash transactions
				$ret->priceServiceCc->importedService =       -1*$rebuiltRefund['rebuiltPrices']['refund']['servicePriceTotal'];
				$ret->priceTaxCc->importedService =           -1*$rebuiltRefund['rebuiltPrices']['refund']['taxTotal'];
				$ret->priceCommissionCc->importedService =    -1*$rebuiltRefund['rebuiltPrices']['refund']['commissionTotal'];
			}
			else {
				// same as above, but fill in cash members
				// rebuildOriginalRefund() called above already handles commissionable, we don't need to handle it again in here
				$ret->priceServiceCash->importedService =     -1*$rebuiltRefund['rebuiltPrices']['refund']['servicePriceTotal'];
				$ret->priceTaxCash->importedService =         -1*$rebuiltRefund['rebuiltPrices']['refund']['taxTotal'];
				$ret->priceCommissionCash->importedService =  -1*$rebuiltRefund['rebuiltPrices']['refund']['commissionTotal'];
			}

			// CC transaction
			if ($isCc) {
				$ret->priceBalancePayable->importedService =  -1*$ret->priceCommissionCc->importedService;
			}
			// VOID-ed transaction
			elseif ($importedService->systemStatus=='cancelled') {
				$ret->priceBalancePayable->importedService = 0.0;
			}
			// SALE, CASH transaction
			else {
				$ret->priceBalancePayable->importedService =
					  -1*$rebuiltRefund['rebuiltPrices']['refund']['servicePriceTotal']
					+ -1*$rebuiltRefund['rebuiltPrices']['refund']['taxTotal']
					- -1*$rebuiltRefund['rebuiltPrices']['refund']['commissionTotal'];
			}
		}
		return $ret;
	}

	/**
	 * Internal helper
	 * @param \App\Bsp\BspRow $amountToDistribuite
	 * @param \App\Bsp\BspRow $record
	 */
	protected function substractFromPriceAttribute(\App\Bsp\BspRow $amountToDistribuite, \App\Bsp\BspRow $record, $attributeStr, $distribuitedAmounts, $key)
	{
		$substractedAmount = null;
		if ($amountToDistribuite->{$attributeStr} && $record->{$attributeStr}) {
			if ($amountToDistribuite->{$attributeStr}->amount >= $record->{$attributeStr}->amount) {
				$substractedAmount = $record->{$attributeStr}->amount;
			}
			else {
				$substractedAmount = $amountToDistribuite->{$attributeStr}->amount;
			}
		}

		if ($substractedAmount!==null) {
			$amountToDistribuite->{$attributeStr}->amount-= 		$substractedAmount;
			$distribuitedAmounts[$key]->{$attributeStr}->amount = 	$substractedAmount;
			$distribuitedAmounts[$key]->{$attributeStr}->currency = ($record->{$attributeStr}->currency?:$amountToDistribuite->currency);
		}

		return $distribuitedAmounts;
	}

	/**
	 * Internal helper
	 * @param \App\Bsp\BspRow $amountToDistribuite
	 * @param \App\Bsp\BspRow $record
	 */
	protected function substractFromTaxAttribute(\App\Bsp\BspRow $amountToDistribuite, \App\Bsp\BspRow $record, $attributeStr, $distribuitedAmounts, $key)
	{
		$substractedAmount = null;
		if ($amountToDistribuite->{$attributeStr} && $record->{$attributeStr}) {
			if ($amountToDistribuite->{$attributeStr}->amount>=$record->{$attributeStr}->amount) {
				$substractedAmount = $record->{$attributeStr}->amount;
			}
			else {
				$substractedAmount = $amountToDistribuite->{$attributeStr}->amount;
			}

			$amountToDistribuite->{$attributeStr}->addTax('sub', -1*$substractedAmount, $amountToDistribuite->currency);
		}
		if ($substractedAmount!==null) {
			$distribuitedAmounts[$key]->{$attributeStr}->addTax('sub', $substractedAmount, ($record->{$attributeStr}->currency?:$amountToDistribuite->currency));
		}

		return $distribuitedAmounts;
	}

	/**
	 * Search the bspRecords list for a certain ticket number
	 */
	protected function searchByTicket($ticketNumber, $airline, $minTicketDate)
	{
		$listObj = new \App\EntityList\BspRecords();
		$listObj->setFilter('ticketNumber', '=', $ticketNumber);
		$listObj->setFilter('supplierCode3', '=', $airline);
		$listObj->setFilter('ticketDate', '>=', $minTicketDate);
		$records = $listObj->exportRecords();
		return array_map(function($r) {
			return $r['id'];
		}, $records);
	}
}
