<?php

namespace apexl\Io\modules\payment\controllers;

use apexl\Config\Singleton;
use apexl\encryption\Encrypt;
use apexl\Io\includes\Controller;
use apexl\Io\includes\Hook;
use apexl\Io\includes\Routes;
use apexl\Io\includes\System;
use apexl\Io\modules\invoice\services\invoiceService;
use apexl\Io\modules\payment\entities\paymentEntity;
use apexl\Io\modules\payment\entities\paymentTokenEntity;
use apexl\Io\modules\payment\entities\userCreditEntity;
use apexl\Io\modules\payment\providers\stripeProvider;
use apexl\Io\modules\payment\services\cardValidationService;

use apexl\Io\modules\payment\services\paymentService;
use apexl\Io\modules\payment\services\woocommerceIntegrationService;
use apexl\Io\modules\product\entities\productEntity;
use apexl\Io\modules\subscription\entities\subscriptionEntity;
use apexl\Io\modules\subscription\services\subscriptionService;
use apexl\Io\modules\user\entities\userEntity;
use apexl\Io\modules\user\services\currentUser;
use apexl\Io\modules\user\services\userTools;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

class paymentController extends Controller{

    public function __construct()
    {
        parent::__construct();
    }

    public function paymentRouting(Request $request, Response $response, $args){

        $body = (object)$request->getQueryParams();
        if (isset($body->args)) $body = json_decode(base64_decode($body->args));
        $useTerms = false;
        foreach ($body->products as $product) {
            $productEntity = new productEntity();
            $productEntity->loadByFrontendId($product->product_id);
            if (isset($productEntity->has_subscription) && $productEntity->has_subscription == 1) {
                $useTerms = true;
                break;
            }
        }

        if ($useTerms) {
            // 1. If has subscription product, send to payment terms
            header('location: ' . rtrim($this->config->app->site->frontend_domain, "/") . '/payment/terms?' . http_build_query($request->getQueryParams())); // Redirect to payment gateway
            //return $this->prePaymentTerms($request, $response, $args);
        } else {
            // Else 2. send straight to Stripe via validation route.
            header('location: ' . rtrim($this->config->app->site->backend_domain, "/") . '/api/v1/payment/action/payment/validation?' . http_build_query($request->getQueryParams())); // Redirect to payment gateway

        }

        exit();
    }

    /**
     * Trigger payments cron - allow sites to set a variable to declare their own versions.
     * @param Request $request
     * @param Response $response
     * @param $args
     * @return Response
     * @throws \Exception
     * @TODO Move into subscription module?
     */
    public function triggerPayments(Request $request, Response $response, $args){
        if (!isset($args['cronKey']) || $args['cronKey'] != $this->config->app->cronKey) {
            $this->output::addMessage('billing.triggerPayments', 'error','cronKey is required');
            return System::asJson($response, [], 404);
        }

        // Suspend subscriptions with too many failed payments (required if 3DSecure not carried out)
        $subscriptionEntity = new subscriptionEntity();
        $subscriptionsToDeactivate = $subscriptionEntity->getOverdueWithFailedPaymentAttempts(4);
        $responseData['subscriptionsSuspended'] = 0;
        foreach ($subscriptionsToDeactivate as $subscription) {
            $subscriptionEntity->load($subscription->id);
            $subscriptionEntity->suspended = TRUE;
            $subscriptionEntity->enabled = FALSE;
            $subscriptionEntity->store();
            Hook::processHook('subscription_disabled', $subscriptionEntity);
            $responseData['subscriptionsSuspended']++;
        }

        $triggerPaymentMethod = System::getVariable('payment_get_default_subscription_trigger') ?? '\\apexl\\Io\\modules\\payment\\services\\paymentService';

        $paymentService = new $triggerPaymentMethod();
        return $paymentService::triggerSubscriptionPayments($request, $response, $args);
    }

    /**
     * Redirect user to payment provider to validate payment details
     * @param Request $request
     * @param Response $response
     * @return Response|void
     * @throws \Exception
     */
    public function getToken(Request $request, Response $response) {

        $body = (object)$request->getQueryParams();
        $data = json_decode(base64_decode($body->args));

        if (
            (!isset($data->userEmail) || !filter_var($data->userEmail, FILTER_VALIDATE_EMAIL))
             || !isset($data->userFirstName)
            || !isset($data->userLastName)
        ) {
            return System::asJson($response, ['error' => 'The following parameters are required: userEmail (valid email address), userFirstName, userLastName'], 400);
        }

        $user = new userEntity();
        if (isset($data->userEmail)) {
            $user->getUserByEmail($data->userEmail);
            if (!$user->id) {
                $password = bin2hex(openssl_random_pseudo_bytes(4));
                $userTools = new userTools();
                $user = $userTools->createCompanyUserWithPassword($request, $data->userEmail, $data->userEmail, $data->userEmail, $password, null);
            }
        }

        $tokenEntity = new paymentTokenEntity();
        $latestToken = $tokenEntity->getLastForUser($user->id);

        if (isset($latestToken['id']) && $latestToken['created'] > (time()-(90*24*60*60)) && $latestToken['setup_complete'] && !$latestToken['abandoned'] && !$latestToken['num_failures']) { // If token created in the last 90 days and it hasn't failed
            $woo = new woocommerceIntegrationService();
            if ($woo->hasWoocommerceIntegration()) {
            $woo->setOrderStatus($data->order_id, 'on-hold', $latestToken['token_iv']); // Set woocommerce order status
            header('location: ' . $data->order_complete_url);
            exit();
        }
        }

        if (!isset($user) || !$user->id) {
            return System::asJson($response, ['error' => 'User could not be found or created'], 400);
        }

        $cardValidationService = new cardValidationService();
        $paymentTokenEntity = $cardValidationService->createNewTokenEntity($user->id, $data);

        $successUrl = $this->config->app->site->backend_domain . Routes::getRoutePattern('payment.getToken.success');
        $cancelUrl = $this->config->app->site->backend_domain . Routes::getRoutePattern('payment.getToken.failure').'?token_id='.$paymentTokenEntity->id;
        $meta = isset($data->meta) ? (array)$data->meta : null;
        $providerUrl = $cardValidationService->getProviderPortalUrl($paymentTokenEntity, $successUrl, $cancelUrl, $meta);

        header('location: ' . $providerUrl); // Redirect to payment gateway
        exit();
    }


    /**
     * Process success response from payment provider
     * @param Request $request
     * @param Response $response
     * @param $args
     * @return void
     * @throws \Exception
     */
    public function getTokenSuccess(Request $request, Response $response, $args)
    {

        // Array ( [permission] => AllowAll [providerSessionId] => cs_test_c110G53f6aPz7Qdy8j7BI01mfoINHepdyI2wsSDj4C7Ga575vBYphw74Fb [localTokenId] => 3 )
        $paymentTokenEntity = new paymentTokenEntity();
        $paymentTokenEntity->load($args['localTokenId']);
        $paymentTokenEntity->encryptProviderSession($args['providerSessionId']);

        $cardValidation = new cardValidationService();

        $data = json_decode($paymentTokenEntity->data);
        if ($paymentTokenEntity->validated) {

            $user = new userEntity();
            $user->load($paymentTokenEntity->user_id);

            $gracePeriodEnds = isset($data->gracePeriodEnds) && $data->gracePeriodEnds ? \DateTime::createFromFormat('Y-m-d', $data->gracePeriodEnds) : null;
            $dateStarts = isset($data->dateStarts) && $data->dateStarts ? \DateTime::createFromFormat('Y-m-d', $data->dateStarts) : null;
            $nextBillingDate = isset($data->nextBillingDate) && $data->nextBillingDate ? $data->nextBillingDate : null;
            $createSubscription = ($data->create_subscription ?? FALSE);
            $user = $cardValidation->completeValidation($request, $paymentTokenEntity, $user->email, $user->display_name, NULL);
            if ($user && $paymentTokenEntity->setup_complete == '1' && $createSubscription) {
                $subscriptionService = new subscriptionService();
                $createdSubscriptions = $subscriptionService->createSubscriptionsWithToken($user, $paymentTokenEntity, $dateStarts, $gracePeriodEnds, $nextBillingDate);
            }


            $meta = isset($data->meta) ? (array)$data->meta : null;
            if (isset($data->chargeNow) && $data->chargeNow > 0) $cardValidation->capturePaymentWithToken($user->id, $data->chargeNow, "", true, $data, $meta, $createSubscription ?? FALSE);

            $woo = new woocommerceIntegrationService();
            if ($woo->hasWoocommerceIntegration()) {
                $paymentTokenEntity->load($paymentTokenEntity->id); // Reload the token entity
                $woo->setOrderStatus($data->order_id, ($paymentTokenEntity->setup_complete ? 'on-hold' : 'failed'), $paymentTokenEntity->token_iv); // Set woocommerce order status
            }
        }

        $subscriptions = array_values($cardValidation->getSubscriptions());
        $replacements = [
            'SUBSCRIPTION_ID' => $subscriptions[0]->id ?? ""
        ];
        foreach ($replacements as $find => $replace) {
            $data->order_complete_url = str_replace("{".$find."}", $replace, $data->order_complete_url);
        }

        header('location: ' . $data->order_complete_url);
        exit();
    }

    /**
     * Deal with failed validation attempts
     * @param Request $request
     * @param Response $response
     * @param $args
     * @return void
     */
    public function getTokenFailure(Request $request, Response $response)
    {
        $body = (object)$request->getQueryParams();

        $paymentTokenEntity = new paymentTokenEntity();
        $paymentTokenEntity->load($body->token_id);
        $data = json_decode($paymentTokenEntity->data);

        $woocommerce = new woocommerceIntegrationService();
        $woocommerce->setOrderStatus($data->order_id, 'failed');

        header('location: ' . $data->order_complete_url);
        exit();
    }

    /**
     * Function to remotely trigger charge of payment token (or decline if requested by woocommerce)
     * @param Request $request
     * @param Response $response
     * @param $args
     * @return Response
     */
    public function chargeToken(Request $request, Response $response, $args)
    {
        $body = (object)$request->getQueryParams();
        $data = json_decode(base64_decode($body->args));

        if (
            (!isset($data->userEmail) || !filter_var($data->userEmail, FILTER_VALIDATE_EMAIL))
            || !isset($data->amount) || !is_numeric($data->amount) || $data->amount < 0.01
            || !isset($data->tokenIv)
            || !isset($data->action)
        ) {
            return System::asJson($response, ['error' => 'The following parameters are required: userEmail (valid email address), total (>0.01), tokenIv (string), action (approve/decline)'], 400);
        }

        $user = new userEntity();
        $user->getUserByEmail($data->userEmail);

        if (!isset($user) || !$user->id) {
            return System::asJson($response, ['error' => 'User could not be found'], 400);
        }

        $tokenEntity = new paymentTokenEntity();
        $latestToken = $tokenEntity->getLastForUser($user->id);

        if (!isset($latestToken['id']) || !$this->compareTokens($latestToken['token_iv'],$data->tokenIv)) {
            return System::asJson($response, ['error' => 'Payment Token could not be found, or provided Token IV does not match'], 400);
        }

        $tokenEntity->load($latestToken['id']);

        if ($tokenEntity->abandoned) return System::asJson($response, ['error' => 'This token has failed too many times and has been abandoned'], 400);
        if (!$tokenEntity->setup_complete) return System::asJson($response, ['error' => 'User did not complete setup of this token'], 400);

        $woo = new woocommerceIntegrationService();
        if ($data->action == 'decline') {
            $woo->setOrderStatus($data->orderId, 'cancelled'); // Set woocommerce order status
        } else {

            $cardValidationService = new cardValidationService();
            $payment = $cardValidationService->capturePaymentWithToken($tokenEntity->user_id, $data->amount, "", false, (object)[
                'tokenId' => $tokenEntity->id,
                'userId' => $tokenEntity->user_id,
                'amount' => $data->amount,
                'paymentType' => 'manually triggered, not linked to subscription',
                'order_id' => $data->orderId ?? NULL,
            ]);

            $success = !is_null($payment) && $payment->status == 'successful' ? 'success' : 'failed';

            if ($success == 'success') {
                $woo->setOrderStatus($data->orderId, 'processing'); // Set woocommerce order status
            } else {
                $woo->setOrderStatus($data->orderId, 'failed'); // Set woocommerce order status
            }
        }

        return System::asJson($response, ['status' => $success, 'tokenId' => $tokenEntity->id, 'amount' => $data->amount, 'action' => $data->action]);
    }

    protected function compareTokens($token1, $token2)
    {
        return preg_replace("/[^a-zA-Z0-9]/", "", $token1) == preg_replace("/[^a-zA-Z0-9]/", "", $token2);
    }

    /**
     * Redirect user to payment provider to validate payment details
     * NOTE - THIS FUNCTION REQUIRES CLEANUP AT SOME POINT
     * @param Request $request
     * @param Response $response
     * @param $args
     * @return Response|void
     * @throws \Exception
     */
    public function validation(Request $request, Response $response, $args) {

        if ((!isset($args['userEmail']) || !filter_var($args['userEmail'], FILTER_VALIDATE_EMAIL)) && (!isset($args['subscriptionCode']) || !$args['subscriptionCode'])) {
            return System::asJson($response, ['error' => 'The following parameters are required: userEmail (valid email address) OR subscriptionCode'], 400);
        }

        $data = (object)[];
        if (isset($args['userEmail']) && filter_var($args['userEmail'], FILTER_VALIDATE_EMAIL)) $data->userEmail = $args['userEmail'];
        if (isset($args['subscriptionCode'])) $data->subscriptionCode = $args['subscriptionCode'];

        $user = new userEntity();

        if (isset($data->userEmail)) {
            $user->getUserByEmail($data->userEmail);
        } elseif (isset($data->subscriptionCode)) {
            $subscriptionEntity = new subscriptionEntity();
            $subscription = $subscriptionEntity->getByCode($data->subscriptionCode);
            $data->productId = $subscription->product_id;
            if (isset($subscription->user_id) && $subscription->user_id) {
                $user->load($subscription->user_id);
            } else {
                return System::asJson($response, ['error' => 'Invalid subscriptionCode'], 400);
            }
        }

        if (!isset($user) || !$user->id) {
            return System::asJson($response, ['error' => 'User could not be found'], 400);
        }

        $cardValidationService = new cardValidationService();
        $paymentTokenEntity = $cardValidationService->createNewTokenEntity($user->id, $data);

        $successUrl = $this->config->app->site->backend_domain . Routes::getRoutePattern('payment.validation.success');
        $cancelUrl = $this->config->app->site->backend_domain . Routes::getRoutePattern('payment.validation.cancel');
        $providerUrl = $cardValidationService->getProviderPortalUrl($paymentTokenEntity, $successUrl, $cancelUrl);

        header('location: ' . $providerUrl); // Redirect to payment gateway
        exit();
    }

    /**
     * If we require users to create an account for their first purchase, set it up here.
     * @param Request $request
     * @param Response $response
     * @return Response
     */
    public function validationActivate(Request $request, Response $response)
    {
        $body = $request->getParsedBody();
        if($body->password != $body->confirm_password){
            $this->output::addMessage($this->path->getRouteName($request).'.validation', 'error', 'Passwords don\'t match');
            $this->output::addResponse($request, [], FALSE); // added so we can hook into this elsewhere.
            return System::asJson($response, [], 400);
        }
        if (!isset($body->localTokenId)) {
            $this->output::addMessage($this->path->getRouteName($request).'.validation', 'error', 'Missing localTokenId');
            $this->output::addResponse($request, [], FALSE); // added so we can hook into this elsewhere.
            return System::asJson($response, [], 400);
        }

        $paymentToken = new paymentTokenEntity();
        $paymentToken->load($body->localTokenId);
        $cardValidation = new cardValidationService();
        $user = $cardValidation->completeValidation($request, $paymentToken, $body->email, $body->fullName, $body->password);

        if ($user) {
            if ($paymentToken->setup_complete == '1') {
                $subscriptionService = new subscriptionService();
                $createdSubscriptions = $subscriptionService->createSubscriptionsWithToken($user, $paymentToken);
            }

            // Log user in
            $userTools = new userTools();
            list($authToken, $refreshToken) = $userTools->startLoggedInSession($user);
            //allow others to act on this response. Hook is keyed by route name.
            $this->output::addResponse($request,['authToken' => $authToken, 'refreshToken' => $refreshToken]);
            $this->output::addMetadata(
                'validationActivate.post.redirect',
                'redirect',
                '/');
            return System::asJson($response);
        }

        $this->output::addMessage($this->path->getRouteName($request).'.validation', 'error', 'Not all of the data provided is valid');
        $this->output::addResponse($request, ['missingFields' => $user->getMissingData()], FALSE); //added so we can hook into this elsewhere.
        return System::asJson($response, [], 400);
    }

    public function oneOffPayment()
    {
        $body = (object)$request->getQueryParams();

        if (!isset($body->products)) {
            return System::asJson($response, ['error' => 'The following request parameters are required: iccid (or userEmail), products (or productId)'], 400);
        }

        $paymentService->createPendingPayment($body->total, $body->order_id);
        $successUrl = $this->config->app->site->backend_domain . Routes::getRoutePattern('payment.oneoff.success').'/'.$body->order_id;
        $cancelUrl = $this->config->app->site->backend_domain . Routes::getRoutePattern('payment.oneoff.cancel');
        $providerUrl = $cardValidationService->getOneTimePaymentUrl($body->products, $successUrl, $cancelUrl);

        header('location: ' . $providerUrl); // Redirect to payment gateway
        exit();
    }

    public function oneOffPaymentSuccess(Request $request, Response $response, $args)
    {
        if (isset($args['remoteOrderId']) && $args['remoteOrderId']) {
            $cardValidationService = new cardValidationService();
            $paymentEntity = new paymentEntity();
            $previousPendingPayment = $paymentEntity->getPendingPaymentByRemoteOrderId($args['remoteOrderId']);
            if (isset($previousPendingPayment['id'])) {
                $paymentEntity->load($previousPendingPayment['id']);
                $paymentEntity->status = 'successful';
                $paymentEntity->provider_ref = $cardValidationService->getPaymentIntentIdFromSessionId($args['providerSessionId']);
                $paymentEntity->store();
            }

            // Post messages to woocommerce webhooks
            $woocommerce = new woocommerceIntegrationService();
            $woocommerce->notifyOrderSuccess($args['remoteOrderId']);

            // Redirect back to woocommerce completion page
            $data = json_decode($paymentEntity->data);
            if (isset($data->order_complete_url)) {
                header('location: ' . $data->order_complete_url);
                exit();
            }
        }
    }

    public function oneOffPaymentCancel()
    {
        // @ToDo redirect back to woocommerce failure page

        header('location: ' . $this->config->app->site->frontend_domain.Routes::getRoutePattern('billing.display.validationfailed', ['localTokenId' => null]));
        exit();
    }

    /**
     * @param Request $request
     * @param Response $response
     * @return Response
     */
    public function paymentConfigurationStore(Request $request, Response $response){
        $body = $request->getParsedBody();

        System::setVariable('io_payment_terms', $body->io_payment_terms);
        if(!empty($body->io_payment_terms_link)) {
            System::setVariable('io_payment_terms_link', $body->io_payment_terms_link);
            System::setVariable('io_payment_terms_link_text', $body->io_payment_terms_link_text);
        } else {
            System::deleteVariable('io_payment_terms_link');
            System::deleteVariable('io_payment_terms_link_text');
        }

        $this->output::addResponse($request);
        $this->output::addMessage($this->path->getRouteName($request).'.validation', 'success', 'Payment Configuration Updated.');
        return System::asJson($response);
    }

    public function paymentTermsAccept(Request $request, Response $response)
    {
        $body = $request->getParsedBody();

        if (!$body->acceptTerms) {
            $this->output::addMessage('acceptTerms.complete', 'error', 'Please tick to understand you accept the terms and conditions');
            $this->output::addResponse($request, [], FALSE);
            return System::asJson($response);
        } else {

            $params = json_decode(base64_decode($body->args));
            if (isset($params->products)) {
                $woocommerceService = new woocommerceIntegrationService();
                if (!$woocommerceService->validateRemoteProductIds($params->products)) {
                    $this->output::addMessage('acceptTerms.complete', 'error', 'At least 1 product not found in Dashboard');
                    $this->output::addResponse($request, [], FALSE);
                    return System::asJson($response);
                }
            }

            $redirectToRoute = System::getVariable('io_payment_terms_accept_redirection') ?? 'billing.validation';
            $redirectToRoute =  ($redirectToRoute == 'billing.validation' ? $this->config->app->site->backend_domain : '') . Routes::getRoutePattern($redirectToRoute);

            /**
             * NOTE this only working with other modules that provide the billing.validation route.
             */
            $this->output::addMetadata(
                'paymentTermsAccepted.post.redirect',
                'redirectToExternal',
                $redirectToRoute.'?args='.$body->args
            );
        }

        return System::asJson($response);

    }

    /**
     * Here's a way to manually complete the setup of tokens.
     * *** NOTE it can only be used in cases where the subscription already exists
     * @param Request $request
     * @param Response $response
     * @return Response
     * @throws \Exception
     */
    public function completeSetup(Request $request, Response $response)
    {

        $body = $request->getParsedBody();

        if (!isset($body->tokenId)) {
            return System::asJson($response, ['error' => 'The following parameters are required: tokenId (int)'], 400);
        }

        $cardValidationService = new cardValidationService();
        $tokenEntity = new paymentTokenEntity();
        $tokenEntity->load($body->tokenId);

        $user = new userEntity();
        $user->load($tokenEntity->user_id);

        if (!isset($user->id) && !$user->id) {
            return System::asJson($response, ['error' => 'User could not be found'], 400);
        }

        $data = [];
        $encrypt = new Encrypt();
        $paymentMethodRefDecrypted = $encrypt->decrypt($tokenEntity->payment_method_ref, $tokenEntity->payment_method_ref_iv, $this->config->app->encryption->key);
        if ($customerId = $cardValidationService->attachCustomerToPaymentMethod($paymentMethodRefDecrypted, $user)) {
            $tokenEntity->user_id = $user->id;
            $tokenEntity->setup_complete = 1; // Mark as setup complete
            $tokenEntity->store();
            $data['success'] = 1;
        } else {
            $data['success'] = 0;
        }

        return System::asJson($response, $data);
    }

    public function remoteUpdate(Request $request, Response $response, $args)
    {
        $body = $request->getParsedBody();
        if (!isset($body->userEmail) || !isset($body->remoteKey) || !isset($this->config->app->remoteUpdates->key) || $body->remoteKey != $this->config->app->remoteUpdates->key) {
            $this->output::addMessage('payment.remote.update', 'error', 'Required information is missing - requirement not stated explicitly for security reasons');
            $this->output::addResponse($request, [], FALSE); // added so we can hook into this elsewhere.
            return System::asJson($response, [], 400);
        }

        $userEntity = new userEntity();
        $userEntity->getUserByEmail($body->userEmail);
        if (!isset($userEntity->id) || !$userEntity->id) {
            $this->output::addMessage('payment.remote.update', 'error', 'User not found');
            $this->output::addResponse($request, [], FALSE); // added so we can hook into this elsewhere.
            return System::asJson($response, [], 400);
        }

        $data = ['creditsUpdated' => 0];
        if (isset($body->creditAmount) && is_numeric($body->creditAmount)) {
            $invoiceService = new invoiceService();
            $invoiceService->updateTotalCredit($userEntity->id, $body->creditAmount, ['reason' => $body->creditReason ?? '']);
            $data['creditsUpdated'] = 1;
        }


        return System::asJson($response, $data);
    }

    public function paymentListTableDataBasic(Request $request, Response $response)
    {
        $params = $request->getQueryParams();
        $components = [];

        $currentUser = currentUser::getCurrentUser();
        $email = $params['userEmail'] ?? $currentUser->email;
        $userEntity = new userEntity();
        $userEntity->getUserByEmail($email);
        $params['user_id'] = $userEntity->id ?? 0;

        $paymentEntity = new paymentEntity();
        $filters = $this->buildFilterConditions($paymentEntity, $params);

        $payments = $paymentEntity->loadMultiple($filters, $params['orderBy'] ?? []); // Note - this inclueds access control with correct config

        $entityData['tableHeader'] = ['#', 'Date', 'Amount', 'Status', 'Subscription #', 'Payment Ref'];

        // @ToDo:  populate subscription IDs
        $paymentSubscriptions = $paymentEntity->getPaymentSubscriptionsByUser($userEntity->id);

        $rows = [];
        foreach ($payments as $payment) {
            $rows[] = [
                'date' => (\DateTime::createFromFormat('U', $payment->created))->format('d M Y'),
                'amount' => $payment->amount,
                'status' => $payment->status,
                'subscriptionIds' => isset($paymentSubscriptions[$payment->id]) ? implode(", ", $paymentSubscriptions[$payment->id]) : '',
                'provider_ref' => $payment->provider_ref,
            ];
        }

        $entityData['rows'] = $rows;
        $entityData['totalData'] = count($rows);

        return System::asJson($response, $entityData);
    }

    public function latestPaymentsTableData(Request $request, Response $response, $args)
    {

        $currentUser = currentUser::getCurrentUser();
        $entityData['tableHeader'] = array_filter(['#', 'Date', $currentUser->isAllowed('administer') ? 'Provider' : null, 'Name', 'Bundle', 'Type', 'Line Items', 'Amount', 'Status']);
        if ($currentUser->isAllowed('administer')) {
            $entityData['tableHeader'][] = 'Next Attempt After';
            $entityData['tableHeader'][] = 'Refs';
            $entityData['tableHeader'][] = 'Error';
        }

        $userEntity = null;
        if (isset($args['userId']) && $args['userId'] > 0) {
            $userEntity = new userEntity();
            $userEntity->load($args['userId']);
        }
        $remoteOrderId = $args['remoteOrderId'] ?? 0;

        $invoiceService = new invoiceService();
        $paymentEntity = new paymentEntity();
        $payments = $paymentEntity->getLatestPaymentsViewData($userEntity, $remoteOrderId);
        $productEntity = new productEntity();
        $subscriptionService = new subscriptionService();
        $rows = [];
        foreach ($payments as $payment) {

            $lineItems = json_decode($payment->lineItems);
            $type = 'PURCHASE';
            $products = [];
            $itemsDesc = [];

            if (is_array($lineItems)) {
                foreach ($lineItems as $item) {
                    $type = $subscriptionService->isLineItemScheduledPayment($item, $payment) ? 'SUBSCRIPTION' : 'PURCHASE';
                    $result = Hook::processHook('modify_is_item_scheduled_payment_label', $item, $payment, $type);
                    if (isset($item->productId)) {
                        $product = $productEntity->getByIdOrCode($item->productId);
                        $products[] = $product['product_name'] ?? '';
                    }
                    $itemsDesc[] = "-" . ($item->description ?? ($item->Description ?? ""));
                }
            }

            switch ($payment->status) {
                case 'successful':
                    $statusClass = 'success';
                    break;
                case 'three_d_secure_required':
                    $statusClass = 'warning';
                    break;
                default:
                    $statusClass = 'danger';
            }

            $row = [
                'id' => $payment->id,
                'paymentDate' => $payment->paymentDate,
                ];
            if ($currentUser->isAllowed('administer')) {
                $row['provider'] = $payment->provider;
            }
            $row = array_merge($row, [
                'customerName' => $payment->customerName,
                'bundle' => implode("<br>", array_unique(array_filter($products))),
                'type' => $type,
                'lineItems' => implode("<br>", $itemsDesc),
                'amount' => $payment->amount,
                'status' => '<span class="fw-bold text-'.$statusClass.' payment-'.$payment->status.'">'.$payment->status.'</span>',
            ]);

            if ($currentUser->isAllowed('administer')) {
                $nextAttempts = $invoiceService->getNextInvoicePaymentAttemptsForUser($payment->user_id);
                $row['next_attempt_after'] = $nextAttempts[$payment->invoice_id] ?? 'never';
                $row['refs'] = implode("<br>", array_filter([$payment->provider_ref, $payment->provider_ref_2]));
                $error = $payment->failure_reason ?? '';
                if ($payment->fraudulent) {
                    $error .= ' <span class="badge rounded-pill bg-danger fw-bold">FRAUD</span>';
                }
                $row['error'] = trim($error);
            }

            $rows[] = $row;
        }

        $entityData['rows'] = $rows;
        $entityData['totalData'] = count($rows);

        $entityData = Hook::processHook('modify_latest_payments_list_table_data', $entityData);

        return System::asJson($response, $entityData);
    }

}