<?php

namespace Hilco\CenposHelper;

use Hilco\CenposHelper\Exceptions\BillingNotActiveForCurrencyException;
use Hilco\CenposHelper\Exceptions\ConvertCryptoException;
use Hilco\CenposHelper\Exceptions\DeleteTokenException;
use Hilco\CenposHelper\Exceptions\GetTokenException;
use GuzzleHttp\Client;
use Hilco\CenposHelper\Exceptions\SiteVerifyException;
use Hilco\CenposHelper\Exceptions\UseCryptoException;
use Hilco\CenposHelper\Exceptions\UseTokenException;
use Hilco\CenposHelper\Exceptions\VoidException;
use Log;

/**
 * Class CenposWrapper
 * @package Hilco\CenposHelper
 */
class CenposWrapper {

    /**
     * GuzzleHTTP Client object for making Cenpos API calls
     * @var
     */
    private $cenposClient;

    /**
     * Encrypted Merchant ID (MID) for a Cenpos account (the "username")
     * @var
     */
    private $encMID;

    /**
     * Encrypted Secret Key for a Cenpos account (the "password")
     * @var
     */
    private $secretKey;

    /**
     * 'moto' or 'ecommerce'
     * @var
     */
    private $industry;

    /**
     * Cenpos account currency code (e.g., 'USD', 'CAD', etc)
     * @var
     */
    private $currency;

    // Cenpos Operations
    const CONVERT_CRYPTO    = 'convertCrypto';
    const DELETE_TOKEN      = 'deleteToken';
    const GET_TOKEN         = 'getToken';
    const USE_CRYPTO        = 'useCrypto';
    const USE_TOKEN         = 'useToken';
    const VOID_TRX          = 'voidTrx';


    /**
     * CenposWrapper constructor.
     * @param $credentials
     * @throws \Exception
     */
    public function __construct ($industry, $currencyCode, $credentials) {
        $baseUri = config('cenpos.base_uri', false);
        if (empty($baseUri)) {
            throw new \Exception("Cenpos base URI env field is empty, unable to create client object.");
        }
        $this->cenposClient = new Client(['base_uri' => $baseUri]);
        $this->encMID = $credentials['merchant_id'];
        $this->secretKey = $credentials['secret_key'];
        $this->industry = $industry;
        $this->currency = $currencyCode;
    }

    /**
     * @param $currencyCode
     * @return CenposWrapper
     * @throws \Exception
     */
    public static function create ($industry, $currencyCode) {
        return new CenposWrapper($industry, $currencyCode, CenposWrapper::getCredentials($currencyCode));
    }

    /**
     * @param $currencyCode
     * @return mixed
     * @throws \Exception
     */
    public static function getCredentials ($currencyCode) {
        $credentials = config("cenpos.accounts.$currencyCode", false);
        if (empty($credentials)) {
            throw new BillingNotActiveForCurrencyException("Credit card billing is not active for the specified currency: $currencyCode");
        }
        return $credentials;
    }

    /**
     * @param $custNo
     * @return string
     * @throws \Exception
     */
    public function getSimpleWebPayQueryParameters ($custNo) {
        try {
            $verifyingPostResponse = self::siteVerify([]);
            if ($verifyingPostResponse['Result'] == 0) {
                $verifyingPost = $verifyingPostResponse['Data'];
                return "verifyingpost=$verifyingPost&sessionToken=true&customerCode=$custNo&isCvv=true";
            } else {
                $resultCode = $verifyingPostResponse['Result'];
                throw new SiteVerifyException(__FUNCTION__ . " did not receive an approved result code from the SiteVerify request: $resultCode");
            }
        } catch (\Exception $e) {
            throw $e;
        }
    }

    /**
     * @param $custNo
     * @return array of credit profile info (Cenpos "tokens") for the customer identified by $custNo
     * @throws \Exception
     */
    public function getCreditProfiles ($custNo) {
        $creditProfiles = [];
        try {
            $cenposTokens = self::getToken($custNo);
            foreach ($cenposTokens as $cenposToken) {
                $creditProfiles[] = [
                    'id'        => $cenposToken['TokenId'],
                    'text'      => $cenposToken['CardNumber'] . ' - ' . $cenposToken['CardExpirationDate'],
                    'last4'     => $cenposToken['CardNumber'],
                    'expDate'   => substr_replace($cenposToken['CardExpirationDate'], '/', 2, 0),
                    'cardType'  => $cenposToken['CardType'],
                ];
            }
            $creditProfiles[] = [
                'id' => 0,
                'text' => 'Use new Card'
            ];

            return $creditProfiles;

        } catch (\Exception $e) {
            throw $e;
        }
    }

    /**
     * @param $saveNewCard
     * @param $token
     * @param $custNo
     * @param $streetAddress
     * @param $city
     * @param $state
     * @param $zip
     * @param $orderNumber
     * @param $authAmount
     * @return array Cenpos authorization response object
     * @throws \Exception
     */
    public function authorizeOneTimeCardPayment ($saveNewCard, $token, $custNo, $streetAddress, $city, $state, $zip, $orderNumber, $authAmount) {
        $addressData = [
            'address'   => $streetAddress,
            'city'      => $city,
            'state'     => $state,
            'zipcode'   => $zip,
        ];

        try {
            if ($saveNewCard) {
                $convertCyprtoResponse = self::convertCrypto($token, $custNo, $addressData);
                $resultCode = \Illuminate\Support\Arr::get($convertCyprtoResponse, 'Result');

                if ($resultCode == 0) {
                    $newCreditProfileId = \Illuminate\Support\Arr::get($convertCyprtoResponse, 'TokenId');
                    return self::useToken($orderNumber, $authAmount, $newCreditProfileId, $addressData, $custNo);
                } else {
                    // Result: 0 is the only valid result code, if we don't get this then ConvertCrypto failed
                    // and we should just return the response so the client can handle the message accordingly.
                    return $convertCyprtoResponse;
                }
            } else {
                return self::useCrypto($orderNumber, $authAmount, $token, $addressData, $custNo);
            }
        } catch (\Exception $e) {
            throw $e;
        }
    }

    /**
     * @param $token
     * @param $custNo
     * @param $streetAddress
     * @param $city
     * @param $state
     * @param $zip
     * @param $orderNumber
     * @param $authAmount
     * @return array Cenpos authorization response object
     * @throws \Exception
     */
    public function authorizeCreditProfilePayment ($token, $custNo, $streetAddress, $city, $state, $zip, $orderNumber, $authAmount) {
        try {
            $addressData = [
                'address'   => $streetAddress,
                'city'      => $city,
                'state'     => $state,
                'zipcode'   => $zip,
            ];
            return self::useToken($orderNumber, $authAmount, $token, $addressData, $custNo);
        } catch (\Exception $e) {
            throw $e;
        }
    }

    /**
     * @param $token
     * @return mixed
     * @throws \Exception
     */
    public function removeCreditProfile ($token) {
        try {
            return self::deleteToken($token);
        } catch (\Exception $e) {
            throw $e;
        }
    }

    /**
     * @param $authRefNum
     * @return mixed Cenpos void authorization response object
     * @throws \Exception
     */
    public function voidAuthorization ($authRefNum) {
        try {
            return self::void($authRefNum);
        } catch (\Exception $e) {
            throw $e;
        }
    }

    /**
     * @param $params
     * @return mixed
     * @throws SiteVerifyException
     */
    private function siteVerify ($params) {
        $params['industry']     = $this->industry;
        $params['merchant']     = $this->encMID;
        $params['secretkey']    = $this->secretKey;

        try {
            $siteVerifyResponse = $this->cenposClient->post('',
                [
                    'query' => [
                        'app' => 'genericcontroller',
                        'action' => 'siteVerify',
                    ],
                    'form_params' => $params,
                    'headers' => [
                        'Content-Type' => 'application/x-www-form-urlencoded'
                    ],
                    'connect_timeout' => 5,
                    'timeout' => 5
                ]
            );
            $siteVerifyResponseBody = $siteVerifyResponse->getBody();
            $siteVerifyResponseContentsJSON = $siteVerifyResponseBody->getContents();

            return json_decode($siteVerifyResponseContentsJSON, true);
        } catch (\Exception $e) {
            throw new SiteVerifyException("Cenpos siteVerify post failed.", $e->getCode(), $e);
        }
    }

    /**
     * @param $custNo
     * @return mixed
     * @throws GetTokenException
     * @throws SiteVerifyException
     */
    private function getToken ($custNo) {
        try {
            $verifyingPostResponse = self::siteVerify(['customercode' => $custNo]);
            if ($verifyingPostResponse['Result'] == 0) {
                $verifyingPost = $verifyingPostResponse['Data'];
            } else {
                $resultCode = $verifyingPostResponse['Result'];
                throw new SiteVerifyException(__FUNCTION__ . " did not receive an approved result code from the SiteVerify request: $resultCode");
            }

            $getTokenResponse = $this->cenposClient->post('api/GetToken/',
                [
                    'form_params' => [
                        'verifyingpost' => $verifyingPost,
                    ],
                    'headers' => [
                        'Content-Type' => 'application/x-www-form-urlencoded',
                    ],
                    'connect_timeout' => 5,
                    'timeout' => 5,
                ]
            );
            $getTokenResponseBody = $getTokenResponse->getBody();
            $getTokenResponseContentsJSON = $getTokenResponseBody->getContents();
            $getTokenResponseArr = json_decode($getTokenResponseContentsJSON, true);

            return \Illuminate\Support\Arr::get($getTokenResponseArr, 'Tokens', []);
        } catch (SiteVerifyException $sve) {
            throw $sve;
        } catch (\Exception $e) {
            throw new GetTokenException("Cenpos GetToken request failed.", $e->getCode(), $e);
        }
    }

    /**
     * @param $tokenId
     * @param $custNo
     * @param $addressData
     * @return mixed
     * @throws ConvertCryptoException
     * @throws SiteVerifyException
     */
    private function convertCrypto ($tokenId, $custNo, $addressData) {
        $params = [
            'tokenid' => $tokenId,
            'customercode' => $custNo,
        ];
        $params = array_merge($params, $addressData);
        try {
            $verifyingPostResponse = self::siteVerify($params);
            if ($verifyingPostResponse['Result'] == 0) {
                $verifyingPost = $verifyingPostResponse['Data'];
            } else {
                $resultCode = $verifyingPostResponse['Result'];
                throw new SiteVerifyException(__FUNCTION__ . " did not receive an approved result code from the SiteVerify request: $resultCode");
            }

            $convertCryptoResponse = $this->cenposClient->post('api/ConvertCrypto/',
                [
                    'form_params' => [
                        'verifyingpost' => $verifyingPost,
                        'tokenid' => $tokenId,
                    ],
                    'headers' => [
                        'Content-Type' => 'application/x-www-form-urlencoded',
                    ],
                ]
            );
            $convertCryptoResponseBody = $convertCryptoResponse->getBody();
            $convertCryptoResponseContentsJSON = $convertCryptoResponseBody->getContents();

            return json_decode($convertCryptoResponseContentsJSON, true);
        } catch (SiteVerifyException $sve) {
            throw $sve;
        } catch (\Exception $e) {
            throw new ConvertCryptoException("Cenpos ConvertCrypto request failed.", $e->getCode(), $e);
        }
    }

    /**
     * @param $invoiceNumber
     * @param $amount
     * @param $tokenId
     * @param $addressData
     * @param $custNo
     * @throws SiteVerifyException
     * @throws UseCryptoException
     * @return mixed
     */
    private function useCrypto ($invoiceNumber, $amount, $tokenId, $addressData, $custNo) {
        $params = [
            'invoicenumber' => $invoiceNumber,
            'amount' => $amount,
            'tokenid' => $tokenId,
            'customercode' => $custNo,
            'currencycode' => $this->currency,
            'type' => 'Auth',
        ];
        $params = array_merge($params, $addressData);
        try {
            $verifyingPostResponse = self::siteVerify($params);
            if ($verifyingPostResponse['Result'] == 0) {
                $verifyingPost = $verifyingPostResponse['Data'];
            } else {
                $resultCode = $verifyingPostResponse['Result'];
                throw new SiteVerifyException(__FUNCTION__ . " did not receive an approved result code from the SiteVerify request: $resultCode");
            }

            $useCryptoResponse = $this->cenposClient->post('api/UseCrypto/',
                [
                    'form_params' => [
                        'verifyingpost' => $verifyingPost,
                        'tokenid' => $tokenId,
                    ],
                    'headers' => [
                        'Content-Type' => 'application/x-www-form-urlencoded',
                    ],
                ]
            );
            $useCryptoResponseBody = $useCryptoResponse->getBody();
            $useCryptoResponseContentsJSON = $useCryptoResponseBody->getContents();
            return json_decode($useCryptoResponseContentsJSON, true);
        } catch (SiteVerifyException $sve) {
            throw $sve;
        } catch (\Exception $e) {
            throw new UseCryptoException("Cenpos UseCrypto request failed.", $e->getCode(), $e);
        }
    }

    /**
     * @param $invoiceNumber
     * @param $amount
     * @param $tokenId
     * @param $addressData
     * @param $custNo
     * @throws SiteVerifyException
     * @throws UseTokenException
     * @return mixed
     */
    private function useToken ($invoiceNumber, $amount, $tokenId, $addressData, $custNo) {
        $params = [
            'invoicenumber' => $invoiceNumber,
            'amount' => $amount,
            'tokenid' => $tokenId,
            'customercode' => $custNo,
            'currencycode' => $this->currency,
            'type' => 'Auth',
        ];
        $params = array_merge($params, $addressData);
        try {
            $verifyingPostResponse = self::siteVerify($params);
            if ($verifyingPostResponse['Result'] == 0) {
                $verifyingPost = $verifyingPostResponse['Data'];
            } else {
                $resultCode = $verifyingPostResponse['Result'];
                throw new SiteVerifyException(__FUNCTION__ . " did not receive an approved result code from the SiteVerify request: $resultCode");
            }

            $useTokenResponse = $this->cenposClient->post('api/UseToken/',
                [
                    'form_params' => [
                        'verifyingpost' => $verifyingPost,
                        'tokenid' => $tokenId,
                    ],
                    'headers' => [
                        'Content-Type' => 'application/x-www-form-urlencoded',
                    ],
                ]
            );
            $useTokenResponseBody = $useTokenResponse->getBody();
            $useTokenResponseContentsJSON = $useTokenResponseBody->getContents();
            return json_decode($useTokenResponseContentsJSON, true);
        } catch (SiteVerifyException $sve) {
            throw $sve;
        } catch (\Exception $e) {
            throw new UseTokenException("Cenpos UseToken request failed.", $e->getCode(), $e);
        }
    }

    /**
     * @param $tokenId
     * @return mixed
     * @throws DeleteTokenException
     * @throws SiteVerifyException
     */
    private function deleteToken ($tokenId) {
        $params = [
            'tokenid' => $tokenId,
        ];
        try {
            $verifyingPostResponse = self::siteVerify($params);
            if ($verifyingPostResponse['Result'] == 0) {
                $verifyingPost = $verifyingPostResponse['Data'];
            } else {
                $resultCode = $verifyingPostResponse['Result'];
                throw new SiteVerifyException(__FUNCTION__ . " did not receive an approved result code from the SiteVerify request: $resultCode");
            }

            $deleteTokenResponse = $this->cenposClient->post('api/DeleteToken/',
                [
                    'form_params' => [
                        'verifyingpost' => $verifyingPost,
                        'tokenid' => $tokenId,
                    ],
                    'headers' => [
                        'Content-Type' => 'application/x-www-form-urlencoded',
                    ],
                ]
            );
            $deleteTokenResponseBody = $deleteTokenResponse->getBody();
            $deleteTokenResponseContentsJSON = $deleteTokenResponseBody->getContents();
            return json_decode($deleteTokenResponseContentsJSON, true);
        } catch (SiteVerifyException $sve) {
            throw $sve;
        } catch (\Exception $e) {
            throw new DeleteTokenException("Cenpos DeleteToken request failed.", $e->getCode(), $e);
        }
    }

    /**
     * @param $referenceNumber
     * @return mixed
     * @throws SiteVerifyException
     * @throws VoidException
     */
    private function void ($referenceNumber) {
        try {
            $verifyingPostResponse = self::siteVerify(['referencenumber' => $referenceNumber]);
            if ($verifyingPostResponse['Result'] == 0) {
                $verifyingPost = $verifyingPostResponse['Data'];
            } else {
                $resultCode = $verifyingPostResponse['Result'];
                throw new SiteVerifyException(__FUNCTION__ . " did not receive an approved result code from the SiteVerify request: $resultCode");
            }

            $voidResponse = $this->cenposClient->post('api/Void/',
                [
                    'form_params' => [
                        'verifyingpost' => $verifyingPost,
                        'referencenumber' => $referenceNumber,
                    ],
                    'headers' => [
                        'Content-Type' => 'application/x-www-form-urlencoded',
                    ],
                    'connect_timeout' => 5,
                    'timeout' => 5,
                ]
            );
            $voidResponseBody = $voidResponse->getBody();
            $voidResponseContentsJSON = $voidResponseBody->getContents();
            return json_decode($voidResponseContentsJSON, true);
        } catch (SiteVerifyException $sve) {
            throw $sve;
        } catch (\Exception $e) {
            throw new VoidException("Cenpos Void request failed.", $e->getCode(), $e);
        }
    }
}