<?php

namespace Sale\Handlers\PaySystem;

use Assist\Exceptions\BadRequestException;
use Assist\Exceptions\ForbiddenException;
use Assist\Exceptions\HttpException;
use Assist\Exceptions\InternalServerErrorException;
use Assist\Exceptions\RequiredParameterDoesNotExistException;
use Assist\Exceptions\UnauthorizedException;
use Assist\Pay\Helper;
use Assist\Pay\ClientHelper;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\ArgumentNullException;
use Bitrix\Main\ArgumentOutOfRangeException;
use Bitrix\Main\Diag\FileLogger;
use Bitrix\Main\Error;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\NotImplementedException;
use Bitrix\Main\Request;
use Bitrix\Main\Type\DateTime;
use Bitrix\Sale\BusinessValue;
use Bitrix\Sale\Order;
use Bitrix\Sale\Payment;
use Bitrix\Sale\PaySystem\IRecurring;
use Bitrix\Sale\PaySystem\IRefund;
use Bitrix\Sale\PaySystem\Service;
use Bitrix\Sale\PaySystem\ServiceHandler;
use Bitrix\Sale\PaySystem\ServiceResult;
use Bitrix\Sale\PriceMaths;
use GuzzleHttp\Exception\GuzzleException;

class AssistHandler extends ServiceHandler implements IRecurring, IRefund
{
    private FileLogger $logger;

    public function __construct($type, Service $service)
    {
        $this->logger = Helper::initLogger();

        parent::__construct($type, $service);
    }

    /**
     * @param Payment|null $payment
     * @param $action
     * @return string
     */
    public function getUrl(Payment $payment = null, $action): string
    {
        $url = parent::getUrl($payment, $action);
        if ($payment !== null && $action === 'payment') {
            $url = $payment->getField(Helper::PAYMENT_FIELD_PS_STATUS_MESSAGE) ?? '';
        }

        return $url;
    }

    /**
     * @param Payment $payment
     * @param Request|null $request
     * @return ServiceResult
     * @throws ArgumentException
     * @throws ArgumentNullException
     * @throws ArgumentOutOfRangeException
     * @throws NotImplementedException
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws HttpException
     * @throws InternalServerErrorException
     * @throws RequiredParameterDoesNotExistException
     * @throws UnauthorizedException
     * @throws GuzzleException
     */
    public function initiatePay(Payment $payment, Request $request = null): ServiceResult
    {
        try {
            $client = ClientHelper::initClient($payment);
            $statusRequest = ClientHelper::getStatusRequest($payment);
            $url = $this->getUrl($payment, 'payment');

            if (!$payment->getField(Helper::PAYMENT_FIELD_PS_STATUS_CODE)) {
                $createPaymentRequest = ClientHelper::getCreatePaymentRequest($payment);

                $result = $client->createPayment($createPaymentRequest);
                $status = $client->orderState($statusRequest);

                $billNumber = $status->getBillNumber();
                $state = $status->getOrderState();

                $url = $result->getUrl();
                $payment->setField(Helper::PAYMENT_FIELD_PS_INVOICE_ID, $billNumber);
                $payment->setField(Helper::PAYMENT_FIELD_PS_STATUS_CODE, $state);
                $payment->setField(Helper::PAYMENT_FIELD_PS_STATUS_MESSAGE, $url);
                $order = $payment->getOrder();
                $order->save();
            } else {
                $status = $client->orderState($statusRequest);
            }

            if ($status->getOrderState() !== 'In Process') {
                $url = null;

                ShowError(Loc::getMessage('STATE_IS_NOT_IN_PROCESS'));
            }

            $this->setExtraParams([
                'SUM' => number_format(PriceMaths::roundPrecision($payment->getSum()), 2, '.', ''),
                'URL' => $url,
            ]);
        } catch (\Exception $exception) {
            ShowError(Loc::getMessage('INIT_PAYMENT_ERROR'));

            throw $exception;
        }

        return $this->showTemplate($payment, 'template');
    }

    /**
     * @return string[]
     */
    public function getCurrencyList(): array
    {
        return ['RUB'];
    }

    /**
     * @param Request $request
     * @param $paySystemId
     * @return bool
     * @throws ArgumentException
     * @throws ArgumentNullException
     * @throws NotImplementedException
     */
    public static function isMyResponse(Request $request, $paySystemId): bool
    {
        $requestOrderAccountNumber = $request->get(Helper::API_PARAM_ORDER_NUMBER);
        $requestBillNumber = $request->get(Helper::API_PARAM_BILL_NUMBER);

        if (!$requestOrderAccountNumber || !$requestBillNumber) {
            return false;
        }

        $order = Order::loadByAccountNumber($requestOrderAccountNumber);

        if (!$order) {
            return false;
        }

        $payments = $order->getPaymentCollection();

        foreach ($payments as $payment) {
            if ($payment->getField(Helper::PAYMENT_FIELD_PS_INVOICE_ID) == $requestBillNumber) {
                return $payment->getField(Helper::PAYMENT_FIELD_PAY_SYSTEM_ID) == $paySystemId;
            }
        }

        return false;
    }

    /**
     * @param Payment $payment
     * @param Request $request
     * @return ServiceResult
     */
    public function processRequest(Payment $payment, Request $request): ServiceResult
    {
        $result = new ServiceResult();

        if ($this->checkSecurity($payment, $request)) {
            $result = $this->setPaymentForOrder($payment, $request, $result);
        }

        return $result;
    }

    /**
     * @param Request $request
     * @return false|mixed
     * @throws ArgumentException
     * @throws ArgumentNullException
     * @throws NotImplementedException
     */
    public function getPaymentIdFromRequest(Request $request): mixed
    {
        $requestOrderAccountNumber = $request->get(Helper::API_PARAM_ORDER_NUMBER);
        $requestBillNumber = $request->get(Helper::API_PARAM_BILL_NUMBER);
        $order = Order::loadByAccountNumber($requestOrderAccountNumber);
        $payments = $order->getPaymentCollection();

        foreach ($payments as $payment) {
            if ($payment->getField(Helper::PAYMENT_FIELD_PS_INVOICE_ID) == $requestBillNumber) {
                return $payment->getId();
            }
        }

        return false;
    }

    /**
     * @param Payment $payment
     * @param Request|null $request
     * @return ServiceResult
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws GuzzleException
     * @throws HttpException
     * @throws InternalServerErrorException
     * @throws RequiredParameterDoesNotExistException
     * @throws UnauthorizedException
     */
    public function repeatRecurrent(Payment $payment, Request $request = null): ServiceResult
    {
        $result = new ServiceResult();
        $client = ClientHelper::initClient($payment);
        $recurrentPaymentRequest = ClientHelper::getRecurrentPaymentRequest($payment);
        $response = $client->recurrentPayment($recurrentPaymentRequest);

        if ($response->getRecurrentPayment()->getOrderState() === Helper::PAYMENT_STATE_APPROVED) {
            $result->setOperationType(ServiceResult::MONEY_COMING);
        }

        return $result;
    }

    /**
     * @param Payment $payment
     * @param Request|null $request
     * @return ServiceResult
     */
    public function cancelRecurrent(Payment $payment, Request $request = null): ServiceResult
    {
        return new ServiceResult();
    }

    /**
     * @param Payment $payment
     * @return bool
     */
    public function isRecurring(Payment $payment): bool
    {
        return $this->getBusinessValue($payment, Helper::PS_OPTION_RECURRENT_PAYMENT) === 'Y';
    }

    /**
     * @param Payment $payment
     * @param $refundableSum
     * @return ServiceResult
     * @throws GuzzleException
     */
    public function refund(Payment $payment, $refundableSum): ServiceResult
    {
        $result = new ServiceResult();
        $client = ClientHelper::initClient($payment);
        $cancelRequest = ClientHelper::getCancelPaymentRequest($payment);

        try {
            $cancelResponse = $client->cancel($cancelRequest);
        } catch (\Exception $exception) {
            $this->debugLog('error: ' . $exception->getMessage());
            $this->debugLog($exception->getTraceAsString());

            $result->addError(new Error('Assist cancel payment error: ' . $exception->getMessage()));

            return $result;
        }

        if ($cancelResponse->getOrder()->getOrderState() === Helper::PAYMENT_STATE_CANCELED) {
            $result->setOperationType(ServiceResult::MONEY_LEAVING);
        }

        return $result;
    }

    /**
     * @param Payment|null $payment
     * @param $code
     * @return mixed
     */
    protected function getBusinessValue(Payment $payment = null, $code): mixed
    {
        $value = BusinessValue::get($code, $this->service->getConsumerName(), $payment);
        if (is_string($value)) {
            $value = trim($value);
        }

        return $value;
    }

    /**
     * @param Payment $payment
     * @param Request $request
     * @param ServiceResult $result
     *
     * @return ServiceResult|false
     */
    private function setPaymentForOrder(Payment $payment, Request $request, ServiceResult $result): ServiceResult|bool
    {
        $requestPaymentStatus = $request->get(Helper::API_PARAM_ORDER_STATE);
        $requestStatusMessage = $request->get(Helper::API_PARAM_CUSTOMER_MESSAGE);

        $requestAmount = (double)$request->get(Helper::API_PARAM_AMOUNT);
        $requestCurrency = $request->get(Helper::API_PARAM_CURRENCY);
        $requestBillNumber = $request->get(Helper::API_PARAM_BILL_NUMBER);

        $paymentAmount = $payment->getSum();
        $paymentCurrency = $payment->getField(Helper::PAYMENT_FIELD_CURRENCY);
        $paymentBillNumber = $payment->getField(Helper::PAYMENT_FIELD_PS_INVOICE_ID);

        if ($requestAmount != $paymentAmount) {
            $this->debugLog('Request amount not equal payment amount.');
            $this->debugLog('request amount: ' . $requestAmount);
            $this->debugLog('payment amount: ' . $paymentAmount);

            return false;
        }

        if ($requestCurrency !== $paymentCurrency) {
            $this->debugLog('Request currency not equal payment currency.');
            $this->debugLog('request currency: ' . $requestCurrency);
            $this->debugLog('payment currency: ' . $paymentCurrency);

            return false;
        }

        if ($requestBillNumber != $paymentBillNumber) {
            $this->debugLog('Request bill number not equal payment bill number.');
            $this->debugLog('request bill number: ' . $requestBillNumber);
            $this->debugLog('payment bill number: ' . $paymentBillNumber);

            return false;
        }

        $result->setPsData([
            Helper::PAYMENT_FIELD_PS_STATUS => 'Y',
            Helper::PAYMENT_FIELD_PS_STATUS_CODE => $requestPaymentStatus,
            Helper::PAYMENT_FIELD_PS_STATUS_MESSAGE => $requestStatusMessage,
            Helper::PAYMENT_FIELD_PS_RESPONSE_DATE => new DateTime(),
            Helper::PAYMENT_FIELD_PS_SUM => $requestAmount,
            Helper::PAYMENT_FIELD_PS_CURRENCY => $requestCurrency,
            Helper::PAYMENT_FIELD_PS_STATUS_DESCRIPTION => ''
        ]);

        if ($requestPaymentStatus === Helper::PAYMENT_STATE_APPROVED || $requestPaymentStatus === Helper::PAYMENT_STATE_DELAYED) {
            $result->setOperationType(ServiceResult::MONEY_COMING);

            if ($requestPaymentStatus === Helper::PAYMENT_STATE_DELAYED) {
                $order = $payment->getOrder();
                $consumerName = $payment->getPaySystem()->getConsumerName();
                $state = BusinessValue::get(Helper::PS_OPTION_ORDER_STATE_AFTER_PAYMENT_WITH_CONFIRM, $consumerName) ?? 'P';
                $order->setField('STATUS_ID', $state);
                $order->save();
            }
        }

        return $result;
    }

    /**
     * @param string $message
     * @return void
     */
    private function debugLog(string $message): void
    {
        if (Helper::isLogsEnabled()) {
            $this->logger->debug($message);
        }
    }

    /**
     * @param Payment $payment
     * @param Request $request
     * @return bool
     */
    private function checkSecurity(Payment $payment, Request $request): bool
    {
        $requestCheckValue = $request->get(Helper::API_PARAM_CHECKVALUE);
        $status = $request->get(Helper::API_PARAM_ORDER_STATE);
        $checkValue = ClientHelper::getCheckValue($payment, true, $status);

        if ($requestCheckValue === $checkValue) {
            return true;
        }

        $this->debugLog('Error check param: checkvalue');
        $this->debugLog('checkvalue in request: ' . $requestCheckValue);
        $this->debugLog('checkvalue: ' . $checkValue);

        return false;
    }
}
