<?php
namespace App\Galileo\StructureParser;

/**
 * This class can process the file content and generate a list of transactions
 * @author Lucian Sirbu
 */
class Processor extends \App\Utils\BaseParser\Structure\StructureParser\Processor
{
	/**
	 * {@inheritDoc}
	 * @see \App\Utils\BaseParser\Structure\StructureParser\Processor::buildTransactionObj()
	 */
	protected function buildTransactionObj(?\App\Utils\BaseParser\Structure\StructureParser\Processor $processor=null, $transactionType=null, $transactionIndexes=null, $classType='Transaction')
	{
		if ($classType=='Transaction') {
			return new \App\Galileo\StructureParser\Structures\Transaction($processor, $transactionType, $transactionIndexes);
		}
		elseif ($classType=='TransactionRefundDistribuited') {
			return new \App\Galileo\StructureParser\Structures\TransactionRefundDistribuited($processor, $transactionType, $transactionIndexes);
		}
		else {
			throw new \Exception("Unknown class type used");
		}
	}

	/**
	 * Scan the internal structure and setup segment associations
	 * @see \App\Utils\BaseParser\Structure\StructureParser\Processor::setupInternalAssociations()
	 */
	protected function setupInternalAssociations($parsedStructure)
	{
		$internalAssociations = [];
		if ($parsedStructure['A08']) {
			foreach ($parsedStructure['A08'] as $k08=>$a08) {
				if ($parsedStructure['A02']) {
					foreach ($parsedStructure['A02'] as $k02=>$a02) {
						if ($a08['A08']['A08FSI']===$a02['A02']['A02FIN']) {
							$internalAssociations['A02->A08'][$k02][] = $k08;
							$internalAssociations['A08->A02'][$k08][] = $k02;
						}
					}
				}

				if ($parsedStructure['A04']) {
					foreach ($parsedStructure['A04'] as $k04=>$a04) {
						if ($a08['A08']['A08ITN']===$a04['A04']['A04ITN']) {
							$internalAssociations['A04->A08'][$k04][] = $k08;	// TODO: fix these. They should be lists, they are not one-to-one
							$internalAssociations['A08->A04'][$k08][] = $k04;
						}
					}
				}
			}
		}

		if ($parsedStructure['A169']) {
			if ($parsedStructure['A02']) {
				foreach ($parsedStructure['A02'] as $k02=>$a02) {
					foreach ($parsedStructure['A169'] as $k169=>$a169) {
						if (($a169['A169']['A16NAM_']['firstname']===$a02['A02']['A02NME_']['firstname']) && ($a169['A169']['A16NAM_']['lastname']===$a02['A02']['A02NME_']['lastname'])) {
							$internalAssociations['A02->A169'][$k02][] = $k169;
							$internalAssociations['A169->A02'][$k169][] = $k02;
						}
					}
				}
			}
		}

		if ($parsedStructure['A29']) {
			$a29 = $parsedStructure['A29'];
			if ($parsedStructure['A02']) {
				foreach ($parsedStructure['A02'] as $k02=>$a02) {
					if (($a29['A29']['A29NAM_']['firstname']===$a02['A02']['A02NME_']['firstname']) && ($a29['A29']['A29NAM_']['lastname']===$a02['A02']['A02NME_']['lastname'])) {
						$internalAssociations['A02->A29'][$k02][] = 0;
					}
				}
			}
		}

		if ($parsedStructure['A166'] && $parsedStructure['A02']) {
			foreach ($parsedStructure['A02'] as $k02=>$a02) {
				foreach ($parsedStructure['A166'] as $k166=>$a166) {
					if (    ($a166['A166OD']['A16ODD_']['nameField']['name']['firstname'] === $a02['A02']['A02NME_']['firstname'])
						 && ($a166['A166OD']['A16ODD_']['nameField']['name']['lastname'] ===  $a02['A02']['A02NME_']['lastname'])) {
						$internalAssociations['A166->A02'][$k166][] = $k02;
						$internalAssociations['A02->A166'][$k02][] = $k166;
					}
				}
			}
		}

		if ($parsedStructure['A168'] && $parsedStructure['A02']) {
			foreach ($parsedStructure['A02'] as $k02=>$a02) {
				foreach ($parsedStructure['A168'] as $k168=>$a168) {
					if (     ($a168['A168OD']['A16ODD_']['nameField']['name']['firstname'] === $a02['A02']['A02NME_']['firstname'])
					      && ($a168['A168OD']['A16ODD_']['nameField']['name']['lastname'] ===  $a02['A02']['A02NME_']['lastname'])) {
						$internalAssociations['A168->A02'][$k168][] = $k02;
						$internalAssociations['A02->A168'][$k02][] = $k168;
					}
				}
			}
		}

		if ($parsedStructure['A27']) {
			if ($parsedStructure['A02'] && $parsedStructure['A27']) {
				foreach ($parsedStructure['A02'] as $k02=>$a02) {
					foreach ($parsedStructure['A27'] as $k27=>$a27) {
						if ($a27['A27']['A27FSI']===$a02['A02']['A02FIN']) {
							$internalAssociations['A02->A27'][$k02][] = $k27;
							$internalAssociations['A27->A02'][$k27][] = $k02;
						}
					}
				}
			}
		}

		if ($parsedStructure['A11']) {
			// there doesn't seem to be a real association between indees between A11 and A02, but they simply seem to one-to-one mapped
			// @2019-10-22 - this never entered production
			// if ($parsedStructure['A02'] && $parsedStructure['A11']) {
			// 	foreach ($parsedStructure['A11'] as $k11=>$a11) {
			// 		if ($parsedStructure['A02'][$k11]) {
			// 			$internalAssociations['A02->A11'][$k11][] = $k11;
			// 			$internalAssociations['A11->A02'][$k11][] = $k11;
			// 		}
			// 	}
			// }

			// there seems to be a relation between A02 and A11, by referencing A02FIN & A11PGR
			// @2020-03-25 - see TD-6428, https://dcsplus.atlassian.net/browse/TD-6428
			// if ($parsedStructure['A02'] && $parsedStructure['A11']) {
			// 	foreach ($parsedStructure['A11'] as $k11=>$a11) {
			// 		foreach ($parsedStructure['A02'] as $k02=>$a02) {
			// 			if (!$a11['A11']['A11PGR'] || !$a02['A02']['A02FIN'] || $a11['A11']['A11PGR']===$a02['A02']['A02FIN']) {
			// 				$internalAssociations['A02->A11'][$k02][] = $k11;
			// 				$internalAssociations['A11->A02'][$k11][] = $k02;
			// 			}
			// 		}
			// 	}
			// }

			// there doesn't seem to be a real association between indees between A11 and A02, they simply seem to be mapped everybody to everybody
			if ($parsedStructure['A02'] && $parsedStructure['A11']) {
				foreach ($parsedStructure['A11'] as $k11=>$a11) {
					foreach ($parsedStructure['A02'] as $k02=>$a02) {
						$internalAssociations['A02->A11'][$k02][] = $k11;
						$internalAssociations['A11->A02'][$k11][] = $k02;
					}
				}
			}
		}

		if ($parsedStructure['A02'] && $parsedStructure['A07']) {
			foreach ($parsedStructure['A02'] as $k02=>$a02) {
				foreach ($parsedStructure['A07'] as $k07=>$a07) {
					if ($a07['A07']['A07FSI']===$a02['A02']['A02FIN']) {
						$internalAssociations['A02->A07'][$k02][] = $k07;
						$internalAssociations['A07->A02'][$k07][] = $k02;
					}
				}
			}
		}

		if ($parsedStructure['A02'] && $parsedStructure['A09']) {
			foreach ($parsedStructure['A02'] as $k02=>$a02) {
				foreach ($parsedStructure['A09'] as $k09=>$a09) {
					if ($a09['A09']['A09FSI']===$a02['A02']['A02FIN']) {
						$internalAssociations['A02->A09'][$k02][] = $k09;
						$internalAssociations['A09->A02'][$k09][] = $k02;
					}
				}
			}
		}

		if ($parsedStructure['A14_NDC'] && $parsedStructure['A02']) {
			foreach ($parsedStructure['A02'] as $k02=>$a02) {
				foreach ($parsedStructure['A14_NDC']['PassengerFares'] as $kA14_NDC=>$A14_NDC) {
					$internalAssociations['A02->A14_NDC'][$k02][] = $kA14_NDC;
					$internalAssociations['A14_NDC->A02'][$kA14_NDC][] = $k02;
				}
			}
		}


		if ($parsedStructure['A14_LCC'] && $parsedStructure['A02']) {
			foreach ($parsedStructure['A14_LCC']['Services'] as $kA14LCC_Service=>$A14LCC_Service) {
				foreach ($parsedStructure['A02'] as $k02=>$a02) {
					foreach ($A14LCC_Service['PassengerFare'] as $kA14_LCC=>$A14_LCC) {
						if (($A14_LCC['passengerIdentifier']-1)===$a02['A02']['A02FIN']) {
							$internalAssociations['A02->A14_LCC'][$k02][] = $kA14_LCC;
							$internalAssociations['A14_LCC->A02'][$kA14_LCC][] = $k02;
						}
					}
				}

			}
		}

		ksort($internalAssociations);

		return $internalAssociations;
	}

	/**
	 * (non-PHPdoc)
	 * @see \App\Utils\BaseParser\Structure\StructureParser\Processor::_getTransactions()
	 */
	protected function _getTransactions()
	{
		$transactions = [];

		$parsedStructure = $this->parsedStructure;
		$structureHandled = false;

		// scan the remarks first, and see if we should handle a NDC booking
		if ($parsedStructure['A14_NDC']) {
			$A14_NDC = $parsedStructure['A14_NDC'];
			if ($A14_NDC['TicketNumbers']) {
				foreach ($A14_NDC['TicketNumbers'] as $ks => $NDC_TKT) {
					foreach ($parsedStructure['A02'] as $k02 => $A02) {
						$NDCTicketNumber = substr($NDC_TKT['TicketNumbers']['ticketNumber'], -10);
						if ($NDCTicketNumber===$A02['A02']['A02TIN']['A02TKT']) {
							$transaction = $this->buildTransactionObj($this, "NDCTicketing", [ 'ks' => $ks, 'k02' => $k02, ]);
							$transaction->setMessagesCollector(new \App\MessagesCollector());
							$structureHandled = true;
							$transactions[] = $transaction->getData();
						}
					}
				}
			}
			if ($A14_NDC['ExtraTransactions']) {
				foreach ($A14_NDC['ExtraTransactions'] as $kx => $NDC_EXTR) {
					if ($NDC_EXTR['RemarksTransactionReason']['type']=='EMD') {
						foreach ($parsedStructure['A02'] as $k02 => $A02) {
							// assume this is associated with all A02s
							$transaction = $this->buildTransactionObj($this, "NDCEMD", [ 'ks'=>0, 'kx' => $kx, 'k02' => $k02, ]);
							$transaction->setMessagesCollector(new \App\MessagesCollector());
							$structureHandled = true;
							$transactions[] = $transaction->getData();
						}
					}

					if ($NDC_EXTR['RemarksTransactionReason']['type']=='ITINERARY MODIFIED INVOL SCHED CHNG') {
						// stop on NDCReissue transactions. They seem to be lacking information, and we only have 1 example for now
						throw new \App\Utils\BaseParser\TextParser\FileParser\Exception\UnhandledFileStructure("Don't know how to handle this NDC file structure[1]. Looks like a reissue, but reissues are not supported for now");
						// foreach ($parsedStructure['A02'] as $k02 => $A02) {
						// 	// assume this is associated with all A02s
						// 	$transaction = $this->buildTransactionObj($this, "NDCReissue", [ 'ks'=>0, 'kx' => $kx, 'k02' => $k02, ]);
						// 	$transaction->setMessagesCollector(new \App\MessagesCollector());
						// 	$structureHandled = true;
						// 	$transactions[] = $transaction->getData();
						// }
					}
				}
			}

			if (!$structureHandled) {
				throw new \App\Utils\BaseParser\TextParser\FileParser\Exception\UnhandledFileStructure("Don't know how to handle this NDC file structure[1]. Unknown file type");
			}
		}
		elseif ($parsedStructure['A14_LCC']) {	// LCC booking
			$passengerFareGroups = [];
			foreach ($parsedStructure['A14_LCC']['Services'] as $ks => $service) {
				foreach ($service['PassengerFare'] as $kpf => $passengerFare) {
					if ($service['PassengerFare']) {
						$passengerFareGroups[$passengerFare['passengerIdentifier']][] = [
							'ks' => $ks,
							'kpf' => $kpf,
							'passengerIdentifier' => (int)$passengerFare['passengerIdentifier']
						];
					}
					else {
						// TODO: remove this, added at 2025-06-03 17:23
						throw new \App\Utils\BaseParser\TextParser\FileParser\Exception\UnhandledFileStructure("Don't know how to handle this NDC file structure[2]. Unknown file type");
					}
				}
			}

			$kpfTotal = count($passengerFareGroups);
			foreach ($passengerFareGroups as $transactionIndexesGroups) {
				$transactionIndex = $transactionIndexesGroups[0];
				$transactionIndex['kpfTotal'] = $kpfTotal;

				$transaction = $this->buildTransactionObj($this, "LCCTicketing", $transactionIndex);
				$transaction->setMessagesCollector(new \App\MessagesCollector());
				$structureHandled = true;
				$transactions[] = $transaction->getData();
			}
		}
		elseif ($parsedStructure['header']['line4']['T50IND']['T50IN12']==='V' && $parsedStructure['A02']) {					// handle VOID's
			foreach ($parsedStructure['A02'] as $k02=>$A02) {
				$transaction = $this->buildTransactionObj($this, "void", [ 'k02'=>$k02 ]);
				$transaction->setMessagesCollector(new \App\MessagesCollector());
				$structureHandled = true;
				$transactions[] = $transaction->getData();
			}
		}
		elseif ($parsedStructure['header']['line4']['T50IND']['T50IN12']==='Z' && $parsedStructure['A02']) {					// handle EMD VOID's
			foreach ($parsedStructure['A02'] as $k02=>$A02) {
				$transaction = $this->buildTransactionObj($this, "void", [ 'k02'=>$k02 ]);
				$transaction->setMessagesCollector(new \App\MessagesCollector());
				$structureHandled = true;
				$transactions[] = $transaction->getData();
			}
		}
		elseif ($parsedStructure['A23']) {																		// handle refunds
			$refundDistributionFailed = false;
			try {
				list($structureHandled, $transactions) = $this->getTransactionFromRefundRaw($transactions, [ 'refundDistributionStrategy' => 'default' ]);
			}
			catch (\App\Utils\BaseParser\Structure\Exception\InvalidRefund $e) {
				$refundDistributionFailed = true;
			}

			if ($refundDistributionFailed) {
				$this->messagesCollector->addInfo("Refund distribution failed, trying to distribute by totals");
				try {
					list($structureHandled, $transactions) = $this->getTransactionFromRefundRaw($transactions, [ 'refundDistributionStrategy' => 'useTotals' ]);
				}
				catch (\App\Utils\BaseParser\Structure\Exception\InvalidRefund $e) {
					$this->messagesCollector->addWarning($e->getMessage());
					$refundDistributionFailed = true;
				}
			}
		}
		elseif ($parsedStructure['A10'] && $parsedStructure['A02']) {											// handle reissues
			foreach ($parsedStructure['A02'] as $k02=>$A02) {
				$transaction = $this->buildTransactionObj($this, "ticketing", [ 'reissue'=>true, 'k02'=>$k02 ]);
				$transaction->setMessagesCollector(new \App\MessagesCollector());
				$structureHandled = true;
				$transactions[] = $transaction->getData();
			}
		}
		elseif ($parsedStructure['A19'] && $parsedStructure['A02']) {											// check if we have an MCO
			// assume 1 MCO mapped to 1'st pax
			$transaction = $this->buildTransactionObj($this, "MCO", [ 'k02'=>0 ]);
			$transaction->setMessagesCollector(new \App\MessagesCollector());
			$structureHandled = true;
			$transactions[] = $transaction->getData();
		}
		elseif ($parsedStructure['EMD'][0]['A29'] && $parsedStructure['A02']) {											// check if we have an EMD
			foreach ($parsedStructure['EMD'] as $kEMD=>$A29) {
				// assume 1 EMD mapped to 1'st pax
				$transaction = $this->buildTransactionObj($this, "EMD", [
					'k02' =>	0,
					'kEMD' =>	$kEMD,
				]);
				$transaction->setMessagesCollector(new \App\MessagesCollector());
				$structureHandled = true;
				$transactions[] = $transaction->getData();
			}
		}
		elseif ($parsedStructure['A04'] && $parsedStructure['A02']) {											// handle ticketing
			foreach ($parsedStructure['A02'] as $k02=>$A02) {
				$transaction = $this->buildTransactionObj($this, "ticketing", [ 'k02'=>$k02 ]);
				$transaction->setMessagesCollector(new \App\MessagesCollector());
				$structureHandled = true;
				$transactions[] = $transaction->getData();
			}
		}

		// check if we have an hotel segment, handle A167
		if ($parsedStructure['A167'] && $parsedStructure['A02']) {
			foreach ($parsedStructure['A02'] as $k02=>$A02) {
				$transaction = $this->buildTransactionObj($this, "hotel", [ 'k02'=>$k02, 'source'=>'A167' ]);
				$transaction->setMessagesCollector(new \App\MessagesCollector());
				$structureHandled = true;
				$transactions[] = $transaction->getData();
			}
		}

		// check if we have an hotel segment, handle A16A
		if ($parsedStructure['A16A'] && $parsedStructure['A02']) {
			foreach ($parsedStructure['A02'] as $k02=>$A02) {
				$transaction = $this->buildTransactionObj($this, "hotel", [ 'k02'=>$k02, 'source'=>'A16A' ]);
				$transaction->setMessagesCollector(new \App\MessagesCollector());
				$structureHandled = true;
				$transactions[] = $transaction->getData();
			}
		}

		// check if we have a car segment, handle A16B
		if ($parsedStructure['A168'] && $parsedStructure['A02']) {
			if ($this->internalAssociations['A168->A02']) {	// if we have the segment associated by name, use that info
				foreach ($this->internalAssociations['A168->A02'] as $k02s) {
					$transaction = $this->buildTransactionObj($this, "car", [ 'k02' => reset($k02s) ]);
					$transaction->setMessagesCollector(new \App\MessagesCollector());
					$structureHandled = true;
					$transactions[] = $transaction->getData();
				}
			}
			else {
				// otherwise assume all passengers are associated
				foreach ($parsedStructure['A02'] as $k02=>$A02) {
					$transaction = $this->buildTransactionObj($this, "car", [ 'k02' => $k02 ]);
					$transaction->setMessagesCollector(new \App\MessagesCollector());
					$structureHandled = true;
					$transactions[] = $transaction->getData();
				}
			}
		}

		// skip surface segments
		if ($parsedStructure['A166'] && $parsedStructure['A02']) {
			foreach ($parsedStructure['A02'] as $k02=>$A02) {
				$transaction = $this->buildTransactionObj($this, "surface", [ 'k02'=>$k02 ]);
				$transaction->setMessagesCollector(new \App\MessagesCollector());
				$structureHandled = true;
				$transactions[] = $transaction->getData();
			}
		}

		if (!$structureHandled && $parsedStructure['header'] && $parsedStructure['A02']) {											// check if we have an EMD
			// ignore this file. It only contains pax data
			$structureHandled = true;
		}

		$this->validateTransactions($transactions, $parsedStructure);

		if (!$structureHandled) {
			throw new \App\Utils\BaseParser\TextParser\FileParser\Exception\UnhandledFileStructure("Don't know how to handle this file structure. Unknown file type");
		}

		// this function is overwritten for Apollo
		$this->triggerPlugins($transactions);

		return $transactions;
	}

	/**
	 * Validate transactions
	 * @param array $transactions
	 * @param array $parsedStructure
	 * @return void
	 * @throws \App\Utils\BaseParser\TextParser\FileParser\Exception\UnhandledFileStructure
	 */
	protected function validateTransactions(array $transactions, array $parsedStructure)
	{
		// check that the sum of LCCTicketing services equals the total paid amount
		$LCCTicketingServices = array_filter($transactions, function($tr) {
			return $tr->getType() == 'LCCTicketing';
		});

		$LCCTransactionTotal = 0;
		if ($LCCTicketingServices) {
			$extractedLCCTicketingTotal = array_sum(array_map(function($tr) {
				return $tr->price->total;
			}, $LCCTicketingServices));

			$LCCTransactionTotal+= $parsedStructure['A14_LCC']['TransactionTotal']['transactionTotal'];

			if (abs($extractedLCCTicketingTotal - $LCCTransactionTotal)>0.0001) {
				throw new \App\Utils\BaseParser\TextParser\FileParser\Exception\UnhandledFileStructure(
					sprintf("The sum of LCCTicketing services does not equal the TransactionTotal. (%.2f, %.2f)", $extractedLCCTicketingTotal, $LCCTransactionTotal)
				);
			}
		}
	}

	/**
	 * this function is overwritten for Apollo
	 */
	protected function triggerPlugins(array $transactions)
	{
		// save the original components
		foreach ($transactions as $tr) {
			if ($tr->price) {
				$paxPrice = $tr->price;

				$paxPrice->originalPriceArr['supplier'] = 				$paxPrice->supplier;
				$paxPrice->originalPriceArr['supplierCurrency'] = 		$paxPrice->supplierCurrency;

				$paxPrice->originalPriceArr['tax'] = 					$paxPrice->tax->getAmount();
				$paxPrice->originalPriceArr['taxCurrency'] = 			$paxPrice->tax->getCurrency();

				$paxPrice->originalPriceArr['commission'] = 			$paxPrice->commission + $paxPrice->commissionVAT;
				$paxPrice->originalPriceArr['commissionCurrency'] = 	$paxPrice->commissionCurrency;
				$paxPrice->originalPriceArr['commissionComponents'] = 	array_map(function($c) {
					return [
						'IOSource' =>		'raw',			// technically we could add stuff from plugins in here when overriding amounts in IO, it's specific to IO
						'category' => 		$c->category,
						'source' => 		$c->source,
						'amount' => 		$c->amount,
						'VAT' => 			$c->VAT,
						'currency' => 		$c->currency,
						'code' => 			$c->code,
					];
				}, $paxPrice->components->getComponentsByCategory('commission'));
			}
		}

		\Engine\Plugins\Manager::getInstance()->executeEvent('Galileo-getTransactions', ['this'=>$this, 'transactions'=>$transactions] );
	}
}
