<?php

namespace App\Core\Payment\Pos;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use App\Core\Payment\Pos\Exceptions\UnsupportedPaymentModelException;
use App\Core\Payment\Pos\Exceptions\UnsupportedTransactionTypeException;
use App\Core\Payment\Pos\PosHelpersTrait;
use Symfony\Component\HttpFoundation\Request;

/**
 * Class EstPos
 * @package Mews\Pos
 */
class VPos implements PosInterface
{
    use PosHelpersTrait;

    /**
     * @const string
     */
    public const NAME = 'VPos';

    /**
     * API URL
     *
     * @var string
     */
    public $url;

    /**
     * 3D Pay Gateway URL
     *
     * @var string
     */
    public $gateway;

    /**
     * Response Codes
     *
     * @var array
     */
    public $codes = [
        '00' => 'approved',
        '01' => 'bank_call',
        '02' => 'bank_call',
        '05' => 'reject',
        '09' => 'try_again',
        '12' => 'invalid_transaction',
        '28' => 'reject',
        '51' => 'insufficient_balance',
        '54' => 'expired_card',
        '57' => 'does_not_allow_card_holder',
        '62' => 'restricted_card',
        '77' => 'request_rejected',
        '99' => 'general_error',
    ];

    /**
     * Transaction Types
     *
     * @var array
     */
    public $types = [
        'pay' => 'Auth',
        'pre' => 'PreAuth',
        'post' => 'PostAuth',
    ];

    /**
     * Currencies
     *
     * @var array
     */
    public $currencies = [];

    /**
     * Transaction Type
     *
     * @var string
     */
    public $type;

    /**
     * API Account
     *
     * @var array
     */
    protected $account = [];

    /**
     * Order Details
     *
     * @var array
     */
    protected $order = [];

    /**
     * Credit Card
     *
     * @var object
     */
    protected $card;

    /**
     * Request
     *
     * @var Request
     */
    protected $request;

    /**
     * Response Raw Data
     *
     * @var object
     */
    protected $data;

    /**
     * Processed Response Data
     *
     * @var mixed
     */
    public $response;

    /**
     * Configuration
     *
     * @var array
     */
    protected $config = [];

    /**
     * EstPos constructor.
     *
     * @param array $config
     * @param mixed $account
     * @param array $currencies
     */
    public function __construct($config, $account, array $currencies)
    {
        $this->config = $config;
        $this->account = $account;
        $this->currencies = $currencies;

        $this->url = isset($this->config['urls'][$this->account->env]) ?
            $this->config['urls'][$this->account->env] :
            $this->config['urls']['production'];

        $this->gateway = isset($this->config['urls']['gateway'][$this->account->env]) ?
            $this->config['urls']['gateway'][$this->account->env] :
            $this->config['urls']['gateway']['production'];


        return $this;
    }

    /**
     * Create Regular Payment XML
     *
     * @return string
     */
    protected function createRegularPaymentXML()
    {
        $nodes = [
            'CC5Request' => [
                'Name' => $this->account->username,
                'Password' => $this->account->password,
                'ClientId' => $this->account->client_id,
                'Type' => $this->type,
                'IPAddress' => $this->order->ip,
                'Email' => $this->order->email,
                'OrderId' => $this->order->id,
                'UserId' => isset($this->order->user_id) ? $this->order->user_id : null,
                'Total' => $this->order->amount,
                'Currency' => $this->order->currency,
                'Taksit' => $this->order->installment,
                'CardType' => isset($this->card->type) ? $this->card->type : null,
                'Number' => $this->card->number,
                'Expires' => $this->card->month . '/' . $this->card->year,
                'Cvv2Val' => $this->card->cvv,
                'Mode' => 'P',
                'GroupId' => '',
                'TransId' => '',
                'BillTo' => [
                    'Name' => $this->order->name ? $this->order->name : null,
                ]
            ]
        ];

        return $this->createXML($nodes, 'ISO-8859-9');
    }

    /**
     * Create Regular Payment Post XML
     *
     * @return string
     */
    protected function createRegularPostXML()
    {
        $nodes = [
            'CC5Request' => [
                'Name' => $this->account->username,
                'Password' => $this->account->password,
                'ClientId' => $this->account->client_id,
                'Type' => $this->types[$this->order->transaction],
                'OrderId' => $this->order->id,
            ]
        ];

        return $this->createXML($nodes, 'ISO-8859-9');
    }

    /**
     * Create 3D Payment XML
     * @return string
     */
    protected function create3DPaymentXML()
    {
        $nodes = [
            'CC5Request' => [
                'Name' => $this->account->username,
                'Password' => $this->account->password,
                'ClientId' => $this->account->client_id,
                'Type' => $this->type,
                'IPAddress' => $this->order->ip,
                'Email' => $this->order->email,
                'OrderId' => $this->order->id,
                'UserId' => isset($this->order->user_id) ? $this->order->user_id : null,
                'Total' => $this->order->amount,
                'Currency' => $this->order->currency,
                'Taksit' => $this->order->installment,
                'Number' => $this->request->get('md'),
                'Expires' => '',
                'Cvv2Val' => '',
                'PayerTxnId' => $this->request->get('xid'),
                'PayerSecurityLevel' => $this->request->get('eci'),
                'PayerAuthenticationCode' => $this->request->get('cavv'),
                'CardholderPresentCode' => '13',
                'Mode' => 'P',
                'GroupId' => '',
                'TransId' => '',
            ]
        ];

        if ($this->order->name) {
            $nodes['BillTo'] = [
                'Name' => $this->order->name,
            ];
        }

        return $this->createXML($nodes, 'ISO-8859-9');
    }

    /**
     * Get ProcReturnCode
     *
     * @return string|null
     */
    protected function getProcReturnCode()
    {
        return isset($this->data->ProcReturnCode) ? (string)$this->data->ProcReturnCode : null;
    }

    /**
     * Get Status Detail Text
     *
     * @return string|null
     */
    protected function getStatusDetail()
    {
        $proc_return_code = $this->getProcReturnCode();

        return $proc_return_code ? (isset($this->codes[$proc_return_code]) ? (string)$this->codes[$proc_return_code] : null) : null;
    }

    /**
     * Create 3D Hash
     *
     * @return string
     */
    public function create3DHash()
    {
        $hash_str = '';

        if ($this->account->model == '3d') {
            $hash_str = $this->account->client_id . $this->order->id . $this->order->amount . $this->order->success_url . $this->order->fail_url . $this->order->rand . $this->account->store_key;
        } elseif ($this->account->model == '3d_pay') {
            $hash_str = $this->account->client_id . $this->order->id . $this->order->amount . $this->order->success_url . $this->order->fail_url . $this->type . $this->order->installment . $this->order->rand . $this->account->store_key;
        }

        return base64_encode(pack('H*',sha1($hash_str)));
    }

    /**
     * Check 3D Hash
     *
     * @param array $data
     * @return bool
     */
    public function check3DHash($data)
    {
        $hash_params = $data['HASHPARAMS'];
        $hash_params_val = $data['HASHPARAMSVAL'];
        $hash_param = $data['HASH'];
        $params_val = '';

        $hashparams_arr = explode(':', $hash_params);
        foreach ($hashparams_arr as $value) {
			if(!empty($value) && isset($data[$value])){
				$params_val = $params_val . $data[$value];
			}
		}

        $hash_val = $params_val . $this->account->store_key;
        $hash = base64_encode(sha1($hash_val, true));

        $return = false;
        if ($hash_params && !($params_val != $hash_params_val || $hash_param != $hash)) {
            $return = true;
        }

        return $return;
    }

    /**
     * Regular Payment
     *
     * @return $this
     * @throws GuzzleException
     */
    public function makeRegularPayment()
    {
        return false;
    }

    /**
     * Make 3D Payment
     *
     * @return $this
     * @throws GuzzleException
     */
    public function make3DPayment()
    {
        return false;
    }

    /**
     * Make 3D Pay Payment
     *
     * @return $this
     */
    public function make3DPayPayment()
    {
        $this->request = Request::createFromGlobals();

        $status = 'declined';

        if ($this->check3DHash($this->request->request->all()) && (string)$this->request->get('ProcReturnCode') == '00') {
            if (in_array($this->request->get('mdStatus'), [1, 2, 3, 4])) {
                $status = 'approved';
            }
        }

        $transaction_security = 'MPI fallback';
        if ($status == 'approved') {
            if ($this->request->get('mdStatus') == '1') {
                $transaction_security = 'Full 3D Secure';
            } elseif (in_array($this->request->get('mdStatus'), [2, 3, 4])) {
                $transaction_security = 'Half 3D Secure';
            }
        }

        $this->response = (object)[
            'id' => (string)$this->request->get('AuthCode'),
            'trans_id' => (string)$this->request->get('TransId'),
            'auth_code' => (string)$this->request->get('AuthCode'),
            'host_ref_num' => (string)$this->request->get('HostRefNum'),
            'response' => (string)$this->request->get('Response'),
            'order_id' => (string)$this->request->get('oid'),
            'transaction_type' => $this->type,
            'transaction' => $this->order->transaction,
            'transaction_security' => $transaction_security,
            'code' => (string)$this->request->get('ProcReturnCode'),
            'md_status' => $this->request->get('mdStatus'),
            'status' => $status,
            'status_detail' => isset($this->codes[$this->request->get('ProcReturnCode')]) ? (string)$this->request->get('ProcReturnCode') : null,
            'hash' => (string)$this->request->get('HASH'),
            'rand' => (string)$this->request->get('rnd'),
            'hash_params' => (string)$this->request->get('HASHPARAMS'),
            'hash_params_val' => (string)$this->request->get('HASHPARAMSVAL'),
            'masked_number' => (string)$this->request->get('maskedCreditCard'),
            'month' => (string)$this->request->get('Ecom_Payment_Card_ExpDate_Month'),
            'year' => (string)$this->request->get('Ecom_Payment_Card_ExpDate_Year'),
            'amount' => (string)$this->request->get('amount'),
            'currency' => (string)$this->request->get('currency'),
            'tx_status' => (string)$this->request->get('txstatus'),
            'eci' => (string)$this->request->get('eci'),
            'cavv' => (string)$this->request->get('cavv'),
            'xid' => (string)$this->request->get('xid'),
            'error_code' => (string)$this->request->get('ErrCode'),
            'error_message' => (string)$this->request->get('ErrMsg'),
            'md_error_message' => (string)$this->request->get('mdErrorMsg'),
            'name' => (string)$this->request->get('firmaadi'),
            'email' => (string)$this->request->get('Email'),
            'campaign_url' => null,
            'extra' => $this->request->get('Extra'),
            'all' => $this->request->request->all(),
        ];

        return $this;
    }

    /**
     * Get 3d Form Data
     *
     * @return array
     */
    public function get3DFormData()
    {
        $data = [];

        if ($this->order) {
            $this->order->hash = $this->create3DHash();

            $inputs = [
                'Version3D' => '2.0',
                'ShopCode' => $this->account->client_id,
                'PurchAmount' => $this->order->amount,
                'Currency' => $this->order->currency,
                'OrderId' => $this->order->id,
                'OkUrl' => $this->order->success_url,
                'FailUrl' => $this->order->fail_url,
                'Rnd' => $this->order->rand,
                'Hash' => $this->order->hash,
                'TxnType' => $this->type,
                'Pan' => $this->card->number,
                'Cvv2' => $this->card->cvv,
                'Expiry' => $this->getCardExpDate(),
                'CardType' => $this->getCardCode(),
                'BonusAmount' => '',
                'Lang' => $this->order->lang
            ];

            if ($this->account->model == '3d_pay') {
                $inputs = array_merge($inputs, [
                    'SecureType' => '3DPay',
                    'InstallmentCount' => $this->order->installment,
                ]);
            }

            $data = [
                'gateway' => $this->gateway,
                'success_url' => $this->order->success_url,
                'fail_url' => $this->order->fail_url,
                'rand' => $this->order->rand,
                'hash' => $this->order->hash,
                'inputs' => $inputs,
            ];
        }

        return $data;
    }

    /**
     * Send contents to WebService
     *
     * @param $contents
     * @return $this
     * @throws GuzzleException
     */
    public function send($contents)
    {
        $client = new Client();

        $response = $client->request('POST', $this->url, [
            'body' => $contents
        ]);

        $this->data = $this->XMLStringToObject($response->getBody()->getContents());

        return $this;
    }

    /**
     * Prepare Order
     *
     * @param object $order
     * @param object null $card
     * @return mixed
     * @throws UnsupportedTransactionTypeException
     */
    public function prepare($order, $card = null)
    {
        $this->type = $this->types['pay'];
        if (isset($order->transaction)) {
            if (array_key_exists($order->transaction, $this->types)) {
                $this->type = $this->types[$order->transaction];
            } else {
                throw new UnsupportedTransactionTypeException('Unsupported transaction type!');
            }
        }

        $this->order = $order;
        $this->card = $card;

        $this->order->installment = $this->order->installment == 0 ? '' : $this->order->installment;
    }

    /**
     * Make Payment
     *
     * @param object $card
     * @return mixed
     * @throws UnsupportedPaymentModelException
     * @throws GuzzleException
     */
    public function payment($card)
    {
        $this->card = $card;

        $model = 'regular';
        if (isset($this->account->model) && $this->account->model) {
            $model = $this->account->model;
        }

        if ($model == 'regular') {
            $this->makeRegularPayment();
        } elseif ($model == '3d') {
            $this->make3DPayment();
        } elseif ($model == '3d_pay') {
            $this->make3DPayPayment();
        } else {
            throw new UnsupportedPaymentModelException();
        }

        return $this;
    }

    /**
     * Refund Order
     *
     * @param array $meta
     * @return $this
     * @throws GuzzleException
     */
    public function refund(array $meta)
    {
        return false;
    }

    /**
     * Cancel Order
     *
     * @param array $meta
     * @return $this
     * @throws GuzzleException
     */
    public function cancel(array $meta)
    {
        return false;
    }

    /**
     * Order Status
     *
     * @param array $meta
     * @return $this
     * @throws GuzzleException
     */
    public function status(array $meta)
    {
        return false;
    }

    /**
     * Order History
     *
     * @param array $meta
     * @return $this
     * @throws GuzzleException
     */
    public function history(array $meta)
    {
        return false;
    }

    /**
     * @return array
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * @return mixed
     */
    public function getAccount()
    {
        return $this->account;
    }

    /**
     * @return array
     */
    public function getCurrencies()
    {
        return $this->currencies;
    }

    /**
     * @return mixed
     */
    public function getOrder()
    {
        return $this->order;
    }

    /**
     * @return mixed
     */
    public function getCard()
    {
        return $this->card;
    }

    /**
     * @return string|null
     */
    public function getCardCode()
    {
        $card_type = null;
        if (isset($this->card->type)) {
            if ($this->card->type == 'visa') {
                $card_type = '0';
            } elseif ($this->card->type == 'master') {
                $card_type = '1';
            }elseif($this->card->type == '1' || $this->card->type == '2'){
                $card_type = $this->card->type;
            }
        }
        return $card_type;
    }

    protected function getCardExpDate()
    {
        $year = (string) substr($this->card->year, 2,2);
        $month = (string) substr($this->card->month, 0,2);

        return (string) $month . $year;
    }


}
