<?php

namespace GlobalPayments\Api\Builders;

use GlobalPayments\Api\Entities\Address;
use GlobalPayments\Api\Entities\AddressCollection;
use GlobalPayments\Api\Entities\Enums\AddressType;
use GlobalPayments\Api\Entities\Enums\PaymentMethodFunction;
use GlobalPayments\Api\Entities\Enums\PaymentMethodName;
use GlobalPayments\Api\Entities\Enums\PaymentMethodType;
use GlobalPayments\Api\Entities\Enums\StatusChangeReason;
use GlobalPayments\Api\Entities\PayFac\UploadDocumentData;
use GlobalPayments\Api\Entities\PayFac\UserReference;
use GlobalPayments\Api\Entities\PaymentStatistics;
use GlobalPayments\Api\Entities\PersonList;
use GlobalPayments\Api\Entities\User;
use GlobalPayments\Api\ServicesContainer;
use GlobalPayments\Api\Entities\PayFac\BankAccountData;
use GlobalPayments\Api\Entities\PayFac\BeneficialOwnerData;
use GlobalPayments\Api\Entities\PayFac\BusinessData;
use GlobalPayments\Api\Entities\PayFac\SignificantOwnerData;
use GlobalPayments\Api\Entities\PayFac\ThreatRiskData;
use GlobalPayments\Api\Entities\PayFac\UserPersonalData;
use GlobalPayments\Api\Entities\Enums\TransactionType;
use GlobalPayments\Api\Entities\Enums\TransactionModifier;
use GlobalPayments\Api\Entities\Exceptions\BuilderException;

/**
 * @property string $userId
 */
class PayFacBuilder extends BaseBuilder
{
    public mixed $transactionType = null;
    public mixed $transactionModifier = null;
    public mixed $bankAccountData = null;
    public mixed $beneficialOwnerData = null;
    public mixed $businessData = null;
    public mixed $significantOwnerData = null;
    public mixed $threatRiskData = null;
    public mixed $userPersonalData = null;
    public mixed $creditCardInformation = null;
    /**
     * @var BankAccountData
     */
    public mixed $achInformation = null;
    public mixed $mailingAddressInformation = null;
    public mixed $secondaryBankInformation = null;
    public mixed $grossBillingInformation = null;
    public ?string $accountNumber = null;
    public ?string $password = null;
    public mixed $accountPermissions = null;
    public ?string $negativeLimit = null;
    public mixed $renewalAccountData = null;
    /** @var UploadDocumentData */
    public mixed $uploadDocumentData = null;
    public mixed $singleSignOnData = null;
    public string|float|null $amount = null;
    public ?string $receivingAccountNumber = null;
    public ?bool $allowPending = null;
    public string|float|null $ccAmount = null;
    public ?bool $requireCCRefund = null;
    public ?string $transNum = null;
    public mixed $flashFundsPaymentCardData = null;
    public ?string $externalId = null;
    public ?string $sourceEmail = null;
    public mixed $deviceDetails = null;
    public mixed $deviceData = null;

    /** @var string */
    public ?string $description = null;

    /** @var string */
    public ?string $gatewayTransactionId = null;

    /** @var string */
    public ?string $globaltransId = null;

    /** @var string */
    public ?string $globalTransSource = null;

    /** @var string */
    public ?string $cardBrandTransactionId = null;

    /**
     * @var array<Product>
     */
    public array $productData = [];

    /**
     * @var PersonList
     */
    public mixed $personsData = null;

    /**
     * @var integer
     */
    public ?int $page = null;

    /**
     * @var integer
     */
    public ?int $pageSize = null;

    /**
     * @var PaymentStatistics
     */
    public mixed $paymentStatistics = null;

    /**
     * @var StatusChangeReason
     */
    public mixed $statusChangeReason = null;

    /** @var User */
    public mixed $userReference = null;

    /**
     * @var array
     */
    public ?array $paymentMethodsFunctions = null;

    /** @var string */
    public ?string $idempotencyKey = null;

    /** @var AddressCollection */
    public mixed $addresses = null;

    /**
     * Required for partners ordering Portico devices. Valid values:
     * [ UTC, PT, MST, MT, CT, ET, HST, AT, AST, AKST, ACT, EET, EAT, MET, NET, PLT,
     * IST, BST, VST, CTT, JST, ACT, AET, SST, NST, MIT, CNT, AGT, CAT]
     *
     * @var string
     */
    public ?string $timezone = null;

    /** @var PaymentMethodName */
    public string $paymentMethodName;

    /** @var PaymentMethodType */
    public string $paymentMethodType;

    public string $currency;

    public string $clientTransactionId;

    const UPLOAD_FILE_TYPES = [
        'tif', 'tiff', 'bmp', 'jpg', 'jpeg', 'gif', 'png', 'doc', 'docx'
    ];

    /**
     *
     * {@inheritdoc}
     *
     * @param TransactionType $type
     *            Request transaction type
     *
     * @return
     */
    public function __construct($type)
    {
        parent::__construct($type);
        $this->transactionType = $type;
        $this->transactionModifier = TransactionModifier::NONE;
    }

    /**
     * Executes the builder against the gateway.
     *
     * @return mixed
     */
    public function execute($configName = 'default')
    {
        parent::execute($configName);
        $client = ServicesContainer::instance()->getPayFac($configName);
        if (method_exists($client, "hasBuiltInMerchantManagementService")
            && $client->hasBuiltInMerchantManagementService()) {
             return $client->processBoardingUser($this);
        }

        return $client->processPayFac($this);
    }

    public function __get($name)
    {
        switch ($name) {
            case 'userId':
                if ($this->userReference instanceof UserReference) {
                    return $this->userReference->userId;
                }
                return null;
            default:
                break;
        }
    }

    public function __isset($name)
    {
        return in_array($name, [
                'userId',
            ]) || isset($this->{$name});
    }

    protected function setupValidations()
    {
        $this->validations->of(
            TransactionType::CREATE_ACCOUNT
        )
            ->with(TransactionModifier::NONE)
            ->check('beneficialOwnerData')->isNotNull()
            ->check('businessData')->isNotNull()
            ->check('userPersonalData')->isNotNull()
            ->check('creditCardInformation')->isNotNull();

        $this->validations->of(
            TransactionType::EDIT |
            TransactionType::RESET_PASSWORD |
            TransactionType::RENEW_ACCOUNT |
            TransactionType::UPDATE_OWNERSHIP_DETAILS |
            TransactionType::DEACTIVATE |
            TransactionType::UPLOAD_CHARGEBACK_DOCUMENT |
            TransactionType::OBTAIN_SSO_KEY |
            TransactionType::UPDATE_BANK_ACCOUNT_OWNERSHIP |
            TransactionType::ADD_FUNDS |
            TransactionType::SWEEP_FUNDS |
            TransactionType::ADD_CARD_FLASH_FUNDS |
            TransactionType::PUSH_MONEY_FLASH_FUNDS |
            TransactionType::SPEND_BACK |
            TransactionType::REVERSE_SPLITPAY |
            TransactionType::SPLIT_FUNDS |
            TransactionType::GET_ACCOUNT_BALANCE
        )
            ->with(TransactionModifier::NONE)
            ->check('accountNumber')->isNotNull();

        $this->validations->of(
            TransactionType::UPDATE_OWNERSHIP_DETAILS
        )
            ->with(TransactionModifier::NONE)
            ->check('beneficialOwnerData')->isNotNull();

        $this->validations->of(
            TransactionType::UPLOAD_CHARGEBACK_DOCUMENT
        )
            ->with(TransactionModifier::NONE)
            ->check('uploadDocumentData')->isNotNull();

        $this->validations->of(
            TransactionType::OBTAIN_SSO_KEY
        )
            ->with(TransactionModifier::NONE)
            ->check('singleSignOnData')->isNotNull();

        $this->validations->of(
            TransactionType::UPDATE_BANK_ACCOUNT_OWNERSHIP
        )
            ->with(TransactionModifier::NONE)
            ->check('beneficialOwnerData')->isNotNull();

        $this->validations->of(
            TransactionType::ADD_FUNDS |
                TransactionType::SWEEP_FUNDS |
                TransactionType::PUSH_MONEY_FLASH_FUNDS |
                TransactionType::SPEND_BACK |
                TransactionType::REVERSE_SPLITPAY |
                TransactionType::SPLIT_FUNDS
        )
                ->with(TransactionModifier::NONE)
                ->check('amount')->isNotNull();

        $this->validations->of(TransactionType::ADD_CARD_FLASH_FUNDS)
                ->with(TransactionModifier::NONE)
                ->check('flashFundsPaymentCardData')->isNotNull();

        $this->validations->of(TransactionType::DISBURSE_FUNDS)
                ->with(TransactionModifier::NONE)
                ->check('receivingAccountNumber')->isNotNull();

        $this->validations->of(TransactionType::SPEND_BACK)
                ->with(TransactionModifier::NONE)
                ->check('allowPending')->isNotNull()
                ->check('receivingAccountNumber')->isNotNull();

        $this->validations->of(TransactionType::SPLIT_FUNDS)
                ->with(TransactionModifier::NONE)
                ->check('accountNumber')->isNotNull()
                ->check('amount')->isNotNull()
                ->check('receivingAccountNumber')->isNotNull();

        $this->validations->of(TransactionType::REVERSE_SPLITPAY)
            ->with(TransactionModifier::NONE)
            ->check('transNum')->isNotNull()
            ->check('requireCCRefund')->isNotNull()
            ->check('ccAmount')->isNotNull();

        $this->validations->of(
            TransactionType::FETCH |
            TransactionType::EDIT
        )
            ->with(TransactionModifier::MERCHANT)
            ->check('userId')->isNotNull();

        $this->validations->of(
            TransactionType::CREATE
        )
            ->with(TransactionModifier::MERCHANT)
            ->check('userPersonalData')->isNotNull();
    }

    /*
     * Primary Bank Account Information - Optional. Used to add a bank account to which funds can be settled
     *
     * var Object GlobalPayments\Api\Entities\PayFac\BankAccountData;
     */
    public function withBankAccountData(BankAccountData $bankAccountData, $paymentMethodFunction = null)
    {
        $this->bankAccountData = $bankAccountData;
        if (!empty($paymentMethodFunction)) {
            $paymentMethodFunction = PaymentMethodFunction::validate($paymentMethodFunction);
            $this->paymentMethodsFunctions[get_class($bankAccountData)] = $paymentMethodFunction;
        }
        return $this;
    }
    /*
     * Merchant Beneficiary Owner Information - Required for all merchants validating KYC based off of personal data
     *
     * var Object GlobalPayments\Api\Entities\PayFac\BeneficialOwnerData;
     */
    public function withBeneficialOwnerData(BeneficialOwnerData $beneficialOwnerData)
    {
        $this->beneficialOwnerData = $beneficialOwnerData;
        return $this;
    }
    /*
     * Business Data - Required for business validated accounts. May also be required for personal validated accounts
     * by ProPay Risk Team
     *
     * var Object GlobalPayments\Api\Entities\PayFac\BusinessData;
     */
    public function withBusinessData(BusinessData $businessData)
    {
        $this->businessData = $businessData;
        return $this;
    }
    /*
     * Significant Owner Information - May be required for some partners based on ProPay Risk decision
     *
     * var Object GlobalPayments\Api\Entities\PayFac\SignificantOwnerData;
     */
    public function withSignificantOwnerData(SignificantOwnerData $significantOwnerData)
    {
        $this->significantOwnerData = $significantOwnerData;
        return $this;
    }
    /*
     * Threat Risk Assessment Information - May be required based on ProPay Risk Decision
     *
     * var Object GlobalPayments\Api\Entities\PayFac\ThreatRiskData;
     */
    public function withThreatRiskData(ThreatRiskData $threatRiskData)
    {
        $this->threatRiskData = $threatRiskData;
        return $this;
    }

    /*
     * User / Merchant Personal Data
     *
     * var Object GlobalPayments\Api\Entities\PayFac\UserPersonalData;
     */
    public function withUserPersonalData(UserPersonalData $userPersonalData)
    {
        $this->userPersonalData = $userPersonalData;
        return $this;
    }
    
    public function withCreditCardData($creditCardInformation, $paymentMethodFunction = null)
    {
        $this->creditCardInformation = $creditCardInformation;
        if (!empty($paymentMethodFunction)) {
            $paymentMethodFunction = PaymentMethodFunction::validate($paymentMethodFunction);
            $this->paymentMethodsFunctions[get_class($creditCardInformation)] = $paymentMethodFunction;
        }

        return $this;
    }
    
    public function withACHData($achInformation)
    {
        $this->achInformation = $achInformation;
        return $this;
    }

    public function withMailingAddress($mailingAddressInformation)
    {
        $this->mailingAddressInformation = $mailingAddressInformation;
        return $this;
    }

    public function withSecondaryBankAccountData($secondaryBankInformation)
    {
        $this->secondaryBankInformation = $secondaryBankInformation;
        return $this;
    }
    
    public function withGrossBillingSettleData($grossBillingInformation)
    {
        $this->grossBillingInformation = $grossBillingInformation;
        return $this;
    }
    
    /*
     * The ProPay account to be updated
     *
     * var int
     */
    public function withAccountNumber($accountNumber)
    {
        $this->accountNumber = $accountNumber;
        return $this;
    }
    
    /*
     * Temporary password which will allow a onetime login to ProPay's website. Must be at least eight characters.
     * Must not contain part or the entire first or last name. Must contain at least one capital letter,
     * one lower case letter, and either one symbol or one number
     *
     * var string
     */
    public function withPassword($password)
    {
        $this->password = $password;
        return $this;
    }
    
    public function withAccountPermissions($accountPermissions)
    {
        $this->accountPermissions = $accountPermissions;
        return $this;
    }
    
    /*
     * amount must be greater than zero
     */
    public function withNegativeLimit($negativeLimit)
    {
        $this->negativeLimit = $negativeLimit;
        return $this;
    }
    
    public function withRenewalAccountData($renewalAccountData)
    {
        $this->renewalAccountData = $renewalAccountData;
        return $this;
    }


    /**
     * /*
     * Document details
     * @param UploadDocumentData  $uploadDocumentData
     * @return $this
     * @throws BuilderException
     */
    public function withUploadDocumentData($uploadDocumentData)
    {
        //file validations
        if (empty($uploadDocumentData->b64_content)) {
            if (
                empty($uploadDocumentData->documentLocation) ||
                !file_exists($uploadDocumentData->documentLocation)
            ) {
                throw new BuilderException('File not found!');
            } elseif (filesize($uploadDocumentData->documentLocation) > 5000000) {
                throw new BuilderException('Max file size 5MB exceeded');
            }

            $fileType = pathinfo($uploadDocumentData->documentLocation, PATHINFO_EXTENSION);
            if (!in_array($fileType, self::UPLOAD_FILE_TYPES)) {
                throw new BuilderException('File type is not supported.');
            }
        }
        $this->uploadDocumentData = $uploadDocumentData;
        return $this;
    }
    
    public function withSingleSignOnData($singleSignOnData)
    {
        $this->singleSignOnData = $singleSignOnData;
        return $this;
    }
    
    public function withAmount($amount)
    {
        $this->amount = $amount;
        return $this;
    }
    
    public function withReceivingAccountNumber($receivingAccountNumber)
    {
        $this->receivingAccountNumber = $receivingAccountNumber;
        return $this;
    }
    
    public function withAllowPending($allowPending)
    {
        $this->allowPending = $allowPending;
        return $this;
    }
    
    public function withCCAmount($ccAmount)
    {
        $this->ccAmount = $ccAmount;
        return $this;
    }

    public function withRequireCCRefund($requireCCRefund)
    {
        $this->requireCCRefund = $requireCCRefund;
        return $this;
    }
    
    public function withTransNum($transNum)
    {
        $this->transNum = $transNum;
        return $this;
    }
    
    public function withFlashFundsPaymentCardData($flashFundsPaymentCardData)
    {
        $this->flashFundsPaymentCardData = $flashFundsPaymentCardData;
        return $this;
    }
    
    public function withExternalId($externalId)
    {
        $this->externalId = $externalId;
        return $this;
    }
    
    public function withSourceEmail($sourceEmail)
    {
        $this->sourceEmail = $sourceEmail;
        return $this;
    }

    public function withGatewayTransactionId($gatewayTransactionId)
    {
        $this->gatewayTransactionId = $gatewayTransactionId;
        return $this;
    }

    public function withGlobaltransId($globaltransId)
    {
        $this->globaltransId = $globaltransId;
        return $this;
    }

    public function withGlobalTransSource($globalTransSource)
    {
        $this->globalTransSource = $globalTransSource;
        return $this;
    }

    public function withCardBrandTransactionId($cardBrandTransactionId)
    {
        $this->cardBrandTransactionId = $cardBrandTransactionId;
        return $this;
    }

    public function withDeviceDetails($deviceDetails)
    {
        $this->deviceDetails = $deviceDetails;
        return $this;
    }

    public function withDeviceData($deviceData)
    {
        $this->deviceData = $deviceData;
        return $this;
    }

    public function withDescription($description)
    {
        $this->description = $description;
        return $this;
    }

    /**
     * Set the request productData
     *
     * @param array $productData Request productData
     *
     * @return $this
     */
    public function withProductData(array $productData)
    {
        $this->productData = $productData;
        return $this;
    }

    /**
     * Set the request customer Data
     *
     * @param PersonList $personsData Request customer Data
     *
     * @return $this
     */
    public function withPersonsData(PersonList $personsData)
    {
        $this->personsData = $personsData;
        return $this;
    }

    public function withUserReference(UserReference $userReference)
    {
        $this->userReference = $userReference;
        return $this;
    }

    public function withModifier($transactionModifier)
    {
        $this->transactionModifier = $transactionModifier;
        return $this;
    }

    public function withPaymentStatistics($paymentStatistics)
    {
        $this->paymentStatistics = $paymentStatistics;
        return $this;
    }

    public function withStatusChangeReason($statusChangeReason)
    {
        $this->statusChangeReason = $statusChangeReason;
        return $this;
    }

    /**
     * Set the gateway paging criteria for the report
     * @param $page
     * @param $pageSize
     * @return $this
     */
    public function withPaging($page, $pageSize)
    {
        $this->page = $page;
        $this->pageSize = $pageSize;
        return $this;
    }

    public function withIdempotencyKey($value)
    {
        $this->idempotencyKey = $value;

        return $this;
    }

    public function withAddress(Address $address, $type = AddressType::BILLING)
    {
        if (!isset($this->addresses)) {
            $this->addresses = new AddressCollection();
        }

        $this->addresses->add($address, $type);

        return $this;
    }

    public function withTimeZone($timezone)
    {
        $this->timezone = $timezone;
        return $this;
    }

    /**
     * @param PaymentMethodType $paymentMethodType
     * @return $this
     */
    public function withPaymentMethodType(string $paymentMethodType)
    {
        $this->paymentMethodType = $paymentMethodType;
        return $this;
    }

    /**
     * @param PaymentMethodName $paymentMethodName
     * @return $this
     */
    public function withPaymentMethodName(string $paymentMethodName)
    {
        $this->paymentMethodName = $paymentMethodName;
        return $this;
    }

    public function withCurrency(string $currency)
    {
        $this->currency = $currency;
        return $this;
    }

    public function withClientTransactionId($value)
    {
        $this->clientTransactionId = $value;
        return $this;
    }
}
