<?php

// TODO: de adaugat filtru dupa data expirare pt toate matching-urile posibile

// TODO: match dupa data exp.

namespace App\Utils\CreditCard;

class MatchCard
{

	/**
	 * The truncated card number
	 * @var string
	 */
	private string $cardNumberTruncated;

	/**
	 * The expiration date - as ISO date
	 * @var string
	 */
	private ?string $expireDate = null;

	/**
	 * Receives the original CC number, cleans it, truncates it and stores it internally.
	 * @param string $cardNumber
	 */
	public function __construct(string $cardNumber, ?string $expireDate=null)
	{

		// eliminate various separators
		$ccNumberDetailsObj = new \App\Utils\CreditCard\NumberDetails($cardNumber);
		$strippedCardNumber = $ccNumberDetailsObj->getStrippedNumber();
		// replace non-digits with '0'
		$cleanedCardNumber = preg_replace('/[^0-9]/', '0', $strippedCardNumber);
		// truncate the number and store it internally
		$cardObj = new NumberDetails($cleanedCardNumber);
		$this->cardNumberTruncated = $cardObj->getMaskedNumber();

		// check and store the expiration date
		if ($expireDate!==null) {
			if (!validDate($expireDate)) {
				throw new \Exception('Invalid expire date for CC matching.');
			}
			$this->expireDate = $expireDate;
		}
	}

	/**
	 * 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
	{

		// 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->matchCardByDetails($ccEnt->cardDetails['truncatedNumber'], $ccEnt->cardDetails['expireDate'])) {
				return $card['id'];
			}
		}

		return false;
	}

	/**
	 * Searches through a certain corporate CCs based on the truncated number.
	 * @param int $idCorporate - the corporate id
	 * @return int|false
	 */
	public function matchByCorporateCreditCards(int $idCorporate) :int|false
	{
		// if an invalid corpoarte id is set - stop the execution
		if ($idCorporate<=0) {
			return false;
		}

		// gets all agency credit cards to match them
		$corpCreditCardsObj = new \App\EntityList\CorporateCreditCards();
		$corpCreditCardsObj->setFilter('corporateCreditCards.idCorporate', '=', $idCorporate);
		$corpCreditCardsList = $corpCreditCardsObj->exportRecords();

		// match by truncated card number
		foreach ($corpCreditCardsList as $card) {
			$ccEnt = \Entity\CorporateCreditCard::selectForUpdate($card['id']);
			if ($this->matchCardByDetails($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 $cardNumber
	 * @param string $expireDate
	 * @return bool
	 */
	private function matchCardByDetails(string $cardNumber, ?string $expireDate) :bool
	{
		// if the expiration date is set in both cases and it is different, then this is not a match
		if ($this->expireDate and $expireDate and $this->expireDate!=$expireDate) {
			return false;
		}

		// at this point if the expire date is filled in both cases, they are equal; if the masked numbers are the same, the card is matched
		if ($this->cardNumberTruncated===$cardNumber) {
			return true;
		}

		// if the card numbers are different it may be because the first 6 digits are masked as well (with 0) (eg. Farelogix) - we set the match as true if the last 4 digits AND the expire date also correspond
		if ($this->expireDate and $expireDate and $this->expireDate===$expireDate) {
			if (substr($this->cardNumberTruncated, 0, 6)==='000000') {
				$cleanedCardNumber = '000000' . substr($cardNumber, 6);
				if ($this->cardNumberTruncated===$cleanedCardNumber) {
					return true;
				}
			}
		}

		return false;
	}

}

