<?php


namespace YooMoney\Model;

use YooKassa\Client;
use YooKassa\Model\ConfirmationType;
use YooKassa\Model\CurrencyCode;
use YooKassa\Model\Payment;
use YooKassa\Model\PaymentData\B2b\Sberbank\VatData;
use YooKassa\Model\PaymentData\B2b\Sberbank\VatDataType;
use YooKassa\Model\PaymentData\PaymentDataB2bSberbank;
use YooKassa\Model\PaymentInterface;
use YooKassa\Model\PaymentMethodType;
use YooKassa\Request\Payments\CreatePaymentRequest;
use YooKassa\Request\Payments\CreatePaymentRequestBuilder;
use YooKassa\Request\Payments\CreatePaymentResponse;
use YooKassa\Request\Payments\Payment\CreateCaptureRequest;
use YooKassa\Request\Payments\Payment\CreateCaptureRequestBuilder;
use YooKassa\Request\Refunds\RefundResponse;
use YooMoney\Helpers\JVersionDependenciesHelper;
use YooMoney\Helpers\LoggerHelper;
use YooMoney\Helpers\YookassaClientFactory;
use YooMoney\Helpers\OauthHelpers\OauthWebhooksHelper;
use YooMoney\Helpers\OauthHelpers\YookassaShopInfoHelper;

require_once JPATH_ROOT.'/components/com_jshopping/payments/pm_yoomoney_sbbol/SbbolException.php';

class KassaPaymentMethod
{
    private $module;
    private $client;
    private $defaultTaxRateId;
    private $defaultTaxSystemCode;
    private $taxRates;
    private $descriptionTemplate;
    private $pmconfigs;
    private $defaultPaymentMode;
    private $defaultPaymentSubject;
    private $defaultDeliveryPaymentMode;
    private $defaultDeliveryPaymentSubject;

    const SHOP_INFO_TEST = 'test';
    const SHOP_INFO_FISCALIZATION_ENABLED = 'fiscalization_enabled';
    const SHOP_INFO_PAYMENT_METHODS = 'payment_methods';

    const CMS_NAME = 'yoo_api_joomshopping_epl';
    const CMS_NAME_OLD = 'yoo_api_joomshopping';

    /**
     * KassaPaymentMethod constructor.
     *
     * @param \pm_yoomoney|\pm_yoomoney_sbbol $module
     * @param array $pmConfig
     */
    public function __construct($module, $pmConfig)
    {
        $this->pmconfigs           = $pmConfig;
        $this->module              = $module;
        $this->descriptionTemplate = !empty($pmConfig['yookassa_description_template'])
            ? $pmConfig['yookassa_description_template']
            : _JSHOP_YOO_DESCRIPTION_DEFAULT_PLACEHOLDER;

        $this->defaultTaxRateId = 1;

        if (!empty($pmConfig['yookassa_default_tax'])) {
            $this->defaultTaxRateId = $pmConfig['yookassa_default_tax'];
        }

        if (!empty($pmConfig['yookassa_default_tax_system'])) {
            $this->defaultTaxSystemCode = $pmConfig['yookassa_default_tax_system'];
        }

        if (!empty($pmConfig['yookassa_default_payment_mode'])) {
            $this->defaultPaymentMode = $pmConfig['yookassa_default_payment_mode'];
        }

        if (!empty($pmConfig['yookassa_default_payment_subject'])) {
            $this->defaultPaymentSubject = $pmConfig['yookassa_default_payment_subject'];
        }

        if (!empty($pmConfig['yookassa_default_delivery_payment_mode'])) {
            $this->defaultDeliveryPaymentMode = $pmConfig['yookassa_default_delivery_payment_mode'];
        }

        if (!empty($pmConfig['yookassa_default_delivery_payment_subject'])) {
            $this->defaultDeliveryPaymentSubject = $pmConfig['yookassa_default_delivery_payment_subject'];
        }

        if ($this->pmconfigs) {
            $this->taxRates = array();
            foreach ($pmConfig as $key => $value) {
                if (strncmp('yookassa_tax_', $key, 13) === 0) {
                    $taxRateId = substr($key, 13);
                    $this->taxRates[$taxRateId] = $value;
                }
            }
        }
    }

    /**
     * @return mixed
     */
    public function getShopId()
    {
        return $this->pmconfigs['shop_id'];
    }

    /**
     * @return void
     */
    public function setShopId($shopId)
    {
        $this->pmconfigs['shop_id'] = $shopId;
    }

    /**
     * @return mixed
     */
    public function getPassword()
    {
        return isset($this->pmconfigs['shop_password']) ? $this->pmconfigs['shop_password'] : null;
    }

    /**
     * @return mixed
     */
    public function getOauthToken()
    {
        return $this->pmconfigs['access_token'];
    }

    /**
     * @param string $token
     * @param int $expiresIn
     * @return void
     */
    public function setOauthToken($token, $expiresIn = null)
    {
        $this->pmconfigs['access_token'] = $token;
        if ($expiresIn) {
            $this->pmconfigs['expires_in'] = $expiresIn;
        }
    }

    /**
     * @return mixed
     */
    public function getKassaMode()
    {
        return $this->pmconfigs['kassamode'];
    }

    /**
     * @return mixed
     */
    public function isTest()
    {
        return $this->pmconfigs['isTest'];
    }

    /**
     * @return mixed
     */
    public function isFiscalizationEnabled()
    {
        return $this->pmconfigs['isFiscalizationEnabled'];
    }

    /**
     * @return mixed
     */
    public function isSberLoanAvailable()
    {
        return $this->pmconfigs['isSberLoanAvailable'];
    }

    /**
     * @return bool
     */
    public function isConnectFailed()
    {
        return $this->pmconfigs['isConnectFailed'];
    }

    /**
     * @return mixed
     */
    public function getState()
    {
        return $this->pmconfigs['state'];
    }

    /**
     * @return void
     */
    public function setState($state)
    {
        $this->pmconfigs['state'] = $state;
    }

    /**
     * @return mixed
     */
    public function getNotificationUrl()
    {
        return $this->pmconfigs['notification_url'];
    }

    /**
     * @return mixed
     */
    public function getPaymentMethodModule()
    {
        return $this->module->pm_method;
    }

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

    /**
     * @param \jshopOrder $order
     * @param \jshopCart $cart
     * @param $returnUrl
     *
     * @return null|CreatePaymentResponse
     *
     * @throws \Exception
     * @since version
     */
    public function createPayment($order, $cart, $returnUrl)
    {
        $this->module->getLogger()->sendHeka(array('payment.create.init'));
        try {
            $builder = CreatePaymentRequest::builder();
            $builder->setAmount($order->order_total)
                    ->setCapture(!$this->isEnableHoldMode())
                    ->setClientIp($_SERVER['REMOTE_ADDR'])
                    ->setDescription($this->createDescription($order))
                    ->setMetadata(array(
                        'order_id'       => $order->order_id,
                        'cms_name'       => self::CMS_NAME,
                        'module_version' => _JSHOP_YOO_VERSION,
                    ));

            $confirmation = array(
                'type'      => ConfirmationType::REDIRECT,
                'returnUrl' => $returnUrl,
            );

            $builder->setConfirmation($confirmation);

            if (count($cart->products) && $this->isSendReceipt()) {
                $this->factoryReceipt($builder, $cart->products, $order);
            }

            $request = $builder->build();
            if ($request->hasReceipt()) {
                $request->getReceipt()->normalize($request->getAmount());
            }
            $this->module->getLogger()->sendHeka(array('payment.create.success'));
        } catch (\Exception $e) {
            $this->module->log('error', 'Failed to build request: '.$e->getMessage());
            $this->module->getLogger()->sendAlertLog('Failed to build request', array(
                'methodid' => 'POST/createPayment',
                'exception' => $e,
            ), array('payment.create.fail'));

            return null;
        }

        try {
            $this->module->getLogger()->sendHeka(array('payment.request.init'));
            $payment = $this->getClient()->createPayment($request);
            $this->module->getLogger()->sendHeka(array('payment.request.success'));
        } catch (\Exception $e) {
            $this->module->log('error', 'Failed to create payment: '.$e->getMessage());
            $this->module->getLogger()->sendAlertLog('Failed to create request', array(
                'methodid' => 'POST/createPayment',
                'exception' => $e,
            ), array('payment.request.fail'));
            throw $e;
        }

        return $payment;
    }


    /**
     * @param $order
     * @param $cart
     * @param $returnUrl
     * @return CreatePaymentResponse|null
     * @throws SbbolException
     */
    public function createSbbolPayment($order, $cart, $returnUrl)
    {
        $this->module->getLogger()->sendHeka(array('payment.create.init'));
        try {
            $pmconfigs = $this->pmconfigs;
            $builder   = CreatePaymentRequest::builder();
            $builder->setAmount($order->order_total)
                    ->setCapture(true)
                    ->setClientIp($_SERVER['REMOTE_ADDR'])
                    ->setDescription($this->createDescription($order))
                    ->setMetadata(array(
                        'order_id'       => $order->order_id,
                        'cms_name'       => self::CMS_NAME_OLD,
                        'module_version' => _JSHOP_YOO_VERSION,
                    ));

            $confirmation = array(
                'type'      => ConfirmationType::REDIRECT,
                'returnUrl' => $returnUrl,
            );

            $usedTaxes = array();
            if (count($cart->products)) {
                foreach ($cart->products as $product) {
                    if (isset($pmconfigs['yoo_sbbol_tax_'.$product['tax_id']])) {
                        $usedTaxes[] = $pmconfigs['yoo_sbbol_tax_'.$product['tax_id']];
                    } else {
                        $usedTaxes[] = $pmconfigs['yoo_sbbol_default_tax'];
                    }
                }
            }

            $usedTaxes = array_unique($usedTaxes);
            if (count($usedTaxes) !== 1) {
                throw new SbbolException();
            }

            $paymentMethodData = new PaymentDataB2bSberbank();
            $vatData           = new VatData();
            $vatType           = reset($usedTaxes);
            if ($vatType !== VatDataType::UNTAXED) {
                $vatData->setType(VatDataType::CALCULATED);
                $vatData->setRate($vatType);
                $vatSum = $order->order_total * $vatType / 100;
                $vatData->setAmount(array('value' => $vatSum, 'currency' => CurrencyCode::RUB));
            } else {
                $vatData->setType(VatDataType::UNTAXED);
            }
            $orderData = json_decode(json_encode($order), true);
            foreach ($orderData as $key => $value) {
                $orderData["%{$key}%"] = $value;
                unset($orderData[$key]);
            }

            $paymentPurpose = strtr($pmconfigs['sbbol_purpose'], $orderData);

            $paymentMethodData->setVatData($vatData);
            $paymentMethodData->setPaymentPurpose($paymentPurpose);
            $builder->setConfirmation($confirmation);
            $builder->setPaymentMethodData($paymentMethodData);
            $request = $builder->build();
            $this->module->getLogger()->sendHeka(array('payment.create.success'));
        } catch (SbbolException $e) {
            $this->module->getLogger()->sendAlertLog('Failed to build request', array(
                'methodid' => 'POST/createSbbolPayment',
                'exception' => $e,
            ), array('payment.create.fail'));
            throw $e;
        } catch (\Exception $e) {
            $this->module->log('error', 'Failed to build request: '.$e->getMessage());
            $this->module->getLogger()->sendAlertLog('Failed to build request', array(
                'methodid' => 'POST/createSbbolPayment',
                'exception' => $e,
            ), array('payment.create.fail'));

            return null;
        }

        try {
            $this->module->getLogger()->sendHeka(array('payment.request.init'));
            $payment = $this->getClient()->createPayment($request);
            $this->module->getLogger()->sendHeka(array('payment.request.success'));
        } catch (\Exception $e) {
            $this->module->log('error', 'Failed to create payment: '.$e->getMessage());
            $this->module->getLogger()->sendAlertLog('Failed to create request', array(
                'methodid' => 'POST/createSbbolPayment',
                'exception' => $e,
            ), array('payment.request.fail'));
            throw $e;
        }

        return $payment;
    }

    /**
     * @param PaymentInterface $payment
     *
     * @return PaymentInterface|null
     */
    public function capturePayment($payment)
    {
        try {
            $this->module->getLogger()->sendHeka(array('capture.request.init'));
            $builder = CreateCaptureRequest::builder();
            $builder->setAmount($payment->getAmount());
            $request  = $builder->build();
            $response = $this->getClient()->capturePayment($request, $payment->getId());
            $this->module->getLogger()->sendHeka(array('capture.request.success'));
        } catch (\Exception $e) {
            $this->module->log('error', 'Failed to create capture payment: '.$e->getMessage());
            $this->module->getLogger()->sendAlertLog('Failed to create capture payment', array(
                'methodid' => 'POST/capturePayment',
                'exception' => $e,
            ), array('capture.request.fail'));

            return null;
        }

        return $response;
    }

    /**
     * @param string $paymentId
     *
     * @return PaymentInterface|null
     */
    public function fetchPayment($paymentId)
    {
        $payment = null;
        try {
            $payment = $this->getClient()->getPaymentInfo($paymentId);
        } catch (\Exception $e) {
            $this->module->log('error', 'Failed to fetch payment information from API: '.$e->getMessage());
        }

        return $payment;
    }

    /**
     * Получает по API Юkassa объект возврата по переданному id
     *
     * @param string $refundId
     *
     * @return RefundResponse|null
     */
    public function fetchRefund($refundId)
    {
        $refund = null;
        try {
            $refund = $this->getClient()->getRefundInfo($refundId);
        } catch (\Exception $e) {
            $this->module->log('error', 'Failed to fetch refund information from API: '.$e->getMessage());
        }

        return $refund;
    }

    /**
     * @param CreatePaymentRequestBuilder|CreateCaptureRequestBuilder $builder
     * @param array $products
     * @param $order
     */
    public function factoryReceipt($builder, $products, $order)
    {
        $this->module->getLogger()->sendHeka(array('receipt.create.init'));
        try {
            $shippingModel = \JSFactory::getTable('shippingMethod', 'jshop');
            $shippingMethods = $shippingModel->getAllShippingMethodsCountry($order->d_country, $order->payment_method_id);
            $defaultTaxRate = $this->defaultTaxRateId;
            if (!empty($order->email)) {
                $builder->setReceiptEmail($order->email);
            }
            if (!empty($order->phone)) {
                $builder->setReceiptPhone(preg_replace('/\D/', '', $order->phone));
            }
            $shipping = false;
            foreach ($shippingMethods as $tmp) {
                if ($tmp->shipping_id == $order->shipping_method_id) {
                    $shipping = $tmp;
                    break;
                }
            }

            $moduleTaxes = \JSFactory::getModel("taxes");
            $allTaxes = $moduleTaxes ? $moduleTaxes->getAllTaxes() : array();
            foreach ($products as $product) {
                if (is_array($product)) {
                    if (isset($product['tax_id']) && !empty($this->taxRates[$product['tax_id']])) {
                        $taxId = $this->taxRates[$product['tax_id']];
                    } else {
                        $taxId = $defaultTaxRate;
                    }
                    $builder->addReceiptItem($product['product_name'], $product['price'], $product['quantity'], $taxId,
                        $this->defaultPaymentMode, $this->defaultPaymentSubject);
                } elseif (is_object($product)) {
                    $taxId = $defaultTaxRate;
                    $suitableTaxes = array_filter($allTaxes, function ($tax) use ($product) {
                        return $product->product_tax == $tax->tax_value;
                    });
                    if (!empty($suitableTaxes)) {
                        $suitableTax = reset($suitableTaxes);
                        if (!empty($this->taxRates[$suitableTax->tax_id])) {
                            $taxId = $this->taxRates[$suitableTax->tax_id];
                        }
                    }
                    $builder->addReceiptItem($product->product_name, $product->product_item_price,
                        $product->product_quantity, $taxId, $this->defaultPaymentMode, $this->defaultPaymentSubject);
                }
            }

            if ($order->shipping_method_id && $shipping) {
                $shippingPrice = $order->order_shipping;
                if (!empty($this->taxRates[$shipping->shipping_tax_id])) {
                    $taxId = $this->taxRates[$shipping->shipping_tax_id];
                    $builder->addReceiptShipping($shipping->name, $shippingPrice, $taxId,
                        $this->defaultDeliveryPaymentMode, $this->defaultDeliveryPaymentSubject);
                } else {
                    $builder->addReceiptShipping($shipping->name, $shippingPrice, $defaultTaxRate,
                        $this->defaultDeliveryPaymentMode, $this->defaultDeliveryPaymentSubject);
                }
            }

            if (!empty($this->defaultTaxSystemCode)) {
                $builder->setTaxSystemCode($this->defaultTaxSystemCode);
            }
            $this->module->getLogger()->sendHeka(['receipt.create.success']);
        } catch (\Exception $e) {
            $this->module->log('error', 'Failed to create receipt: '.$e->getMessage());
            $this->module->getLogger()->sendAlertLog('Failed to create receipt', array(
                'methodid' => 'POST/factoryReceipt',
                'exception' => $e,
            ), array('receipt.create.fail'));
        }
    }

    /**
     * @return Client
     */
    public function getClient()
    {
        if ($this->client === null) {
            $clientFactory = $this->getYookassaClientFactory();
            $this->client = $clientFactory->getClient();
        }

        return $this->client;
    }

    /**
     * @param \jshopOrder $order
     *
     * @return string
     */
    private function createDescription($order)
    {
        $descriptionTemplate = $this->descriptionTemplate;

        $replace = array();
        foreach ($order as $property => $value) {
            $replace['%'.$property.'%'] = $value;
        }

        $description = strtr($descriptionTemplate, $replace);

        return (string)mb_substr($description, 0, Payment::MAX_LENGTH_DESCRIPTION);
    }

    /**
     * @return bool
     */
    public function isEnableHoldMode()
    {
        return isset($this->pmconfigs['yookassa_enable_hold_mode']) && $this->pmconfigs['yookassa_enable_hold_mode'] == '1';
    }

    /**
     * @return bool
     */
    public function isSendReceipt()
    {
        return isset($this->pmconfigs['yookassa_send_check']) && $this->pmconfigs['yookassa_send_check'] == '1';
    }

    /**
     * @return bool
     */
    public function isSendSecondReceipt()
    {
        return isset($this->pmconfigs['send_second_receipt']) && $this->pmconfigs['send_second_receipt'] == '1';
    }

    /**
     * @return int
     */
    public function getSecondReceiptStatus()
    {
        return (int)$this->pmconfigs['kassa_second_receipt_status'];
    }

    /**
     * Запрос информации о магазине и его установленных вебхуках
     *
     * @return void
     * @throws \Exception
     */
    public function setShopAndWebhooksInfo()
    {
        if (!$this->getKassaMode()) {
            return;
        }

        if ($this->isAuthorization()) {
            $this->setShopInfo($this->getShopInfo());
        }

        if ($this->isOauthAuthorization() && !$this->isConnectFailed()) {
            $this->checkWebhooks();
        }
    }

    /**
     * Проверка на наличие вебхуков
     *
     * @return void
     * @throws \Exception
     */
    public function checkWebhooks()
    {
        $webhookHelper = $this->getOauthWebhooksHelper();
        $this->pmconfigs['notification_url'] = $webhookHelper->getCurrentWebhookUrl();

        if (!$webhookHelper->checkWebhooks() || !$this->getNotificationUrl()) {
            $this->pmconfigs['isConnectFailed'] = true;
        }
    }

    /**
     * @param array|null $shopInfo
     * @return void
     */
    private function setShopInfo($shopInfo)
    {
        $this->pmconfigs['isTest'] = true;
        $this->pmconfigs['isFiscalizationEnabled'] = false;
        $this->pmconfigs['isSberLoanAvailable'] = false;

        if ($shopInfo) {
            $this->pmconfigs['isTest'] = isset($shopInfo[self::SHOP_INFO_TEST]) && $shopInfo[self::SHOP_INFO_TEST];
            $this->pmconfigs['isFiscalizationEnabled'] = isset($shopInfo[self::SHOP_INFO_FISCALIZATION_ENABLED]) && $shopInfo[self::SHOP_INFO_FISCALIZATION_ENABLED];
            $this->pmconfigs['isSberLoanAvailable'] = isset($shopInfo[self::SHOP_INFO_PAYMENT_METHODS]) && in_array(PaymentMethodType::SBER_LOAN, $shopInfo[self::SHOP_INFO_PAYMENT_METHODS]);
        }
    }

    /**
     * Проверка на подключение и получение информации о магазине
     *
     * @return array
     * @throws \Exception
     */
    public function getShopInfo()
    {
        $this->pmconfigs['isConnectFailed'] = false;

        try {
            $shopInfo = $this->getYookassaShopInfoHelper()->fetchShopInfo();
        } catch (\Exception $e) {
            $this->module->log('error', 'Failed to fetch shop info: ' . $e->getMessage());
            $this->pmconfigs['isConnectFailed'] = true;
            return array();
        }

        return $shopInfo;
    }

    /**
     * @return OauthWebhooksHelper
     */
    private function getOauthWebhooksHelper()
    {
        return new OauthWebhooksHelper(
            new LoggerHelper(),
            $this->getYookassaClientFactory(),
            new JVersionDependenciesHelper()
        );
    }

    /**
     * @return YookassaShopInfoHelper
     */
    private function getYookassaShopInfoHelper()
    {
        return new YookassaShopInfoHelper($this->getYookassaClientFactory());
    }

    /**
     * @return YookassaClientFactory
     */
    private function getYookassaClientFactory()
    {
        return new YookassaClientFactory(new LoggerHelper(), $this);
    }

    /**
     * Проверка на авторизацию по oauth
     *
     * @return bool
     */
    public function isOauthAuthorization()
    {
        return $this->getShopId() && $this->getOauthToken();
    }

    /**
     * Проверка на basic авторизацию
     *
     * @return bool
     */
    public function isBasicAuthorization()
    {
        return $this->getShopId()
            && $this->getPassword()
            && !$this->getOauthToken();
    }

    /**
     * Проверка, пройдена ли какая-либо авторизация
     *
     * @return bool
     */
    public function isAuthorization()
    {
        return $this->isOauthAuthorization() || $this->isBasicAuthorization();
    }
}
