<?php

namespace App\Utils\CreditCard;


/**
 * Partial re-implementation of MatchCard, designed specifically for GDS-related matching
 * USE THIS CLASS ONLY WHEN YOU KNOW WHAT YOU'RE DOING
 * @package App\Utils\CreditCard
 */
class MatchCardFromGDS extends \App\Utils\CreditCard\MatchCard
{
	/**
	 * Searches through agency CCs based on the truncated number.
	 * @return int|false - the matched agency credit card (if found); null otherwise
	 */
	public function matchByAgencyCreditCards() :int|false
	{
		if (!$this->cardNumberTruncated) {
			return false;	// refuse to match without card number
		}

		if (!$this->expireDate) {
			return false;	// refuse to match without expiration date
		}

		// gets all agency credit cards to match them
		$agencyCreditCardsObj = new \App\EntityList\AgencyCreditCards();
		$agencyCreditCardsList = $agencyCreditCardsObj->exportRecords();

		// match by truncated card number
		foreach ($agencyCreditCardsList as $card) {
			$ccEnt = \Entity\AgencyCreditCard::selectForUpdate($card['id']);
			if ($this->matchCardLooseByDetails($ccEnt->cardDetails['truncatedNumber'], $ccEnt->cardDetails['expireDate'])) {
				return $card['id'];
			}
		}

		return false;
	}

	/**
	 * Compares the internally stored CC details with this current card.
	 * It has two types of matching: 1. by full masked number (first 6 + last 4) and expire date
	 * if set in both cases or 2. last 4 digits AND the expire date must be set in both cases.
	 * @param string $DBcardNumber
	 * @param string $DBexpireDate
	 * @return bool
	 */
	private function matchCardLooseByDetails(string $DBcardNumber, ?string $DBexpireDate) :bool
	{
		// refuse to match without expiration date
		if (!$this->expireDate || !$DBexpireDate) {
			return false;
		}

		// if the expiration date is different, then this is not a match
		if ($this->expireDate!==$DBexpireDate) {
			return false;
		}

		// if the masked numbers are the same, the card is considered matched
		if ($this->cardNumberTruncated===$DBcardNumber) {
			return true;
		}


		/**
		 * Next, do some loose matching
		 * We have some situations that need some loose matching:
		 *  - some CC companies have different card lengths:
		 *		- Visa: 			13, 16, or 19 digits
		 *		- Mastercard: 		16 digits
		 *		- American Express: 15 digits
		 *		- Discover: 		16 digits
		 *		- Diners Club: 		14 digits
		 *		- JCB: 				16 digits
		 *		- Maestro: 			12-19 digits (most variable)
		 * 		- notice that mostly standardise on 16-digits cards
		 *  - TINA doesn't implement the "standard" company 2-letter codes. @see \App\Utils\CreditCard\NumberDetails::COMPANIES_CODES, so we cannot realiably match/differentiate companies
		 * 		- this is done like this because VISA might be exposed as VI, BB, BA, BF, CG for example.
		 * 		- if we really need company matching we might add more mappings in the future.
		 *  - TINA doesn't validate handle variable lengths when matching, it simply encodes the middle of the CC number as XXXXX (variable number of X'es)
		 *  - on the other hand, Farelogix will send only the last 4 digits of the card, so the parser will pad with '0' to make it 16 hardcoded digits,
		 * 		but the CC in TINA might not be 16 digits long, so we need to match only the last 4 digits
		 *  - because of this we need to match first 6/last 4 digits instead of full CC match, as the card lengths might vary
		 * */

		// match only the first 6 digits and last 4 digits
		if (substr($this->cardNumberTruncated, 0, 6)===substr($DBcardNumber, 0, 6) && substr($this->cardNumberTruncated, -4)===substr($DBcardNumber, -4)) {
			return true;
		}

		// if the first 6 digits are '000000', match only the last 4 digits. Here we assume the expiration date matched correctly. An improvement would be to also match the CC company
		if (substr($this->cardNumberTruncated, 0, 6)==='000000' && substr($this->cardNumberTruncated, -4)===substr($DBcardNumber, -4)) {
			return true;
		}

		return false;
	}

}
