<?php

namespace apexl\Io\modules\payment\services;

use apexl\Config\Singleton;
use apexl\encryption\Encrypt;
use apexl\Io\includes\Routes;
use apexl\Io\includes\System;
use apexl\Io\modules\email\services\emailService;
use apexl\Io\modules\email\services\templateService;
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\product\entities\productEntity;
use apexl\Io\modules\subscription\entities\subscriptionCreditEntity;
use apexl\Io\modules\subscription\entities\subscriptionEntity;
use apexl\Io\modules\user\entities\userEntity;
use apexl\Io\services\Logger;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

class paymentService {

    protected $provider;
    protected $providerName;
    protected $config;

    public function __construct()
    {
        $this->provider = new stripeProvider();
        $this->providerName = 'stripe';
        $this->config = Singleton::getInstance();
    }

    public function getTokenProviderData($token)
    {
        $encrypt = new Encrypt();
        if (!$token->provider_data) {
            $config = Singleton::getInstance();
            $paymentMethodRefDecrypted = $encrypt->decrypt($token->payment_method_ref, $token->payment_method_ref_iv, $config->app->encryption->key);
            $paymentMethod = $this->provider->getPaymentMethod($paymentMethodRefDecrypted);
            if ($paymentMethod) {
                $providerData = (object)[
                    'brand' => $paymentMethod->card->brand,
                    'funding' => $paymentMethod->card->funding,
                    'last4' => $paymentMethod->card->last4,
                    'exp_month' => $paymentMethod->card->exp_month,
                    'exp_year' => $paymentMethod->card->exp_year,
                ];
                $paymentToken = new paymentTokenEntity();
                $paymentToken->load($token->id);
                if (isset($paymentToken->id) && $paymentToken->id = $token->id) {
                    $paymentToken->provider_data = json_encode($providerData);
                    $paymentToken->store();
                    return $paymentToken->provider_data;
                }
            }
        } else {
            $providerData = json_decode($token->provider_data);
            return $providerData;
        }

        return null;
    }

    public function getPaymentTokensUsedForOrders()
    {
        $paymentTokenEntity = new paymentTokenEntity();
        $tokens = $paymentTokenEntity->fetchAllSetupComplete();

        $orderCards = [];
        foreach ($tokens as $token) {
            if (strpos($token->data, '"order_id":"') !== false) {
                $decodedData = json_decode($token->data);
                $orderCards[$decodedData->order_id] = $token;
            }
        }

        return $orderCards;
    }

    public function getBillingToken($userId, $paymentProvider)
    {
        $paymentTokenEntity = new paymentTokenEntity();
        return $paymentTokenEntity->getBillingTokenForUser($userId, $paymentProvider); // Get last payment token for user
    }

    public function getCardSetupLink($user)
    {
        $args = (object)[
            'userEmail' => $user->email,
            'total' => 0,
            'create_subscription' => 0,
            'addPaymentMethod' => 1
        ];
        $url = $this->config->app->site->backend_domain . Routes::getRoutePattern('billing.validation', [], FALSE) . '?args='.base64_encode(json_encode($args));

        return $url;
    }

    public function fraudAlertEmail($fraudEmailAddress)
    {
        $emailService = new emailService();
        $result = $emailService->send(
            (object) [
                'to' => "[ADMINISTRATOR]",
                'subject' => 'Fraud Alert',
                'body' => templateService::fetch("generic", [
                    'subject' => 'Fraud Alert',
            'message' => "An order has just been blocked as it was placed by $fraudEmailAddress, an account that has previously been marked as fraudulent"
                ]),
                'user_id' => 0,
            ],
            'generic'
        );

        Logger::log('order_blocked_by_fraud_email', ['email' => $fraudEmailAddress, 'sent' => $result]);

        return;
    }

    public function createPendingPayment($amount, $data=[], $remoteOrderId=0)
    {

        $payment = new paymentEntity();

        $previousPendingPayment = $payment->getPendingPaymentByRemoteOrderId($remoteOrderId);
        if (isset($previousPendingPayment['id'])) {
            $payment->load($previousPendingPayment['id']);
        }

        $payment->provider = $this->providerName;
        $payment->provider_ref = "";
        $payment->amount = $amount;
        $payment->status = "pending";
        if ($remoteOrderId) $payment->remote_order_id = $remoteOrderId;
        if ($data) $payment->data = json_encode($data);

        $payment->store();

        return $payment;
    }

    /**
     * Charge subscription payments that are due to be billed today
     * @param Request $request
     * @param Response $response
     * @param $args
     * @return Response
     * @throws \Exception
     */
    public static function triggerSubscriptionPayments(Request $request, Response $response, $args)
    {

        die('Pdisabled - superseeded by subscription-billing.trigger.cron AND payment.trigger.cron in io-invoice');

        $subscriptionEntity = new subscriptionEntity();
        $subscriptionCreditEntity = new subscriptionCreditEntity();
        $productEntity = new productEntity();
        $cardValidationService = new cardValidationService();
        $toBill = $subscriptionEntity->getSubscriptionsToBill(date('Y-m-d'), 10);
        $subscriptionsProcessed = 0;

        foreach ($toBill as $userId => $subscriptions) {

            // remove subscriptions that have had a payment attempt in the last X hours
            $time = new \DateTime('now');
            $timeInterval = 24*60; // Don't try and take payment again if it was taken in last 24 hours
            $time->sub(new \DateInterval("PT".$timeInterval."M"));
            $paymentEntity = new paymentEntity();
            foreach ($subscriptions as $id => $subscription) {
                $payment = $paymentEntity->getMostRecentForSubscription($subscription->id);
                if ($payment && $payment->created > $time->format('U')) unset($subscriptions[$id]);
            }

            $totalCharge = 0;
            $totalNet = 0;
            $totalTax = 0;
            $subscriptionsBilled = [];
            $gracePeriodSubscriptions = [];

            foreach ($subscriptions as $subscription) {

                $subscriptionsBilled[] = $subscription;
                $incCharge = !$subscription->grace_period_ends || $subscription->next_billing_date >= $subscription->grace_period_ends; // If this is the grace period, subtract the base subscription charge
                if (!$incCharge) $gracePeriodSubscriptions[] = $subscription;

                $productEntity->load($subscription->product_id);
                if ($incCharge && isset($productEntity->id)) {
                    $total = round($subscription->product_quantity*$productEntity->price, 2);
                    $vat = round($total*0.2, 2);
                    $totalCharge += $total + $vat;

                    /* NOTE*** CREDITS ARE APPLIED IN THIS FUNCTION: $cardValidationService->capturePaymentWithToken
                     * $credits = $subscriptionCreditEntity->getForSubscription($subscription->id, ($subscription->last_billing_date ?? '2020-01-01'), $subscription->next_billing_date);
                    foreach ($credits as $credit) {
                        $creditAmount = round($credit->credit_amount*1.2, 2);
                        $totalCharge -= $creditAmount;
                    }*/

                    $totalNet += $total;
                    $totalTax += $vat;
                }

                $subscriptionsProcessed++;
            }

            if ($totalCharge > 0) {
                $cardValidationService->setSubscriptions($subscriptionsBilled);
                $paymentEntity = $cardValidationService->capturePaymentWithToken($subscription->user_id, $totalCharge, "", true, [], null, true);
                $success = isset($paymentEntity->status) && in_array($paymentEntity->status, ['successful','three_d_secure_required']);
            } elseif (count($gracePeriodSubscriptions)) { // If no charges made, make sure subscriptions that were still in the grace period are moved on one-month
                $cardValidationService->setSubscriptions($gracePeriodSubscriptions);
            }

            foreach ($subscriptions as $subscription) {
                $subscriptionEntity->load($subscription->id);
                $subscriptionEntity->last_checked = date('Y-m-d H:i:s');
                $subscriptionEntity->store();
            }
        }

        return System::asJson($response, ['subscriptionsProcessed' => $subscriptionsProcessed]);
    }
    public function disableActiveThreeDSecureLinks($subscriptionId)
    {
        $paymentEntity = new paymentEntity();
        $payments = $paymentEntity->getActiveThreeDSecurePaymentsForSubscription($subscriptionId);
        foreach ($payments as $payment) {
            $paymentEntity = new paymentEntity();
            $paymentEntity->load($payment->id, true);
            $paymentEntity->status = 'failed';
            $paymentEntity->store();
        }

        return;
    }

    public function reattemptPayment($paymentEntity)
    {

        // If not already successful, mark as failed
        if ($paymentEntity->status != "successful") {
            $paymentEntity->status = 'failed';
            $paymentEntity->store();
        }

        $subscriptionEntity = new subscriptionEntity();
        $subscriptionEntity->getByPaymentId($paymentEntity->id);
        $cardValidationService = new cardValidationService();
        if (isset($subscriptionEntity->id) && $subscriptionEntity->id) {
            $cardValidationService->setSubscriptions([$subscriptionEntity]);
        }

        return $cardValidationService->capturePaymentWithToken($paymentEntity->user_id, $paymentEntity->amount, "", true, json_decode($paymentEntity->data), null, true, true);
    }

    public function getCardDataForToken($token)
    {
        $encrypt = new Encrypt();
        $config = Singleton::getInstance();
        $stripe = $this->provider;

        $token = (object)$token;
        if (!$token->provider_data) {
            $paymentMethodRefDecrypted = $encrypt->decrypt($token->payment_method_ref, $token->payment_method_ref_iv, $config->app->encryption->key);
            $paymentMethod = $stripe->getPaymentMethod($paymentMethodRefDecrypted);
            if ($paymentMethod) {
                $providerData = (object)[
                    'brand' => $paymentMethod->card->brand,
                    'funding' => $paymentMethod->card->funding,
                    'last4' => $paymentMethod->card->last4,
                    'exp_month' => $paymentMethod->card->exp_month,
                    'exp_year' => $paymentMethod->card->exp_year,
                ];
                $paymentToken = new paymentTokenEntity();
                $paymentToken->load($token->id);
                if (isset($paymentToken->id) && $paymentToken->id = $token->id) {
                    $paymentToken->provider_data = json_encode($providerData);
                    $paymentToken->store();
                }

                return $providerData;
            }
        } else {
            $providerData = json_decode($token->provider_data);
            return $providerData;
        }

        return null;
    }

    public function investigate()
    {

        die();

        $this->provider->updateCustomer('cus_MZ1vvdOXrXlTX1', ['email' => 'bevbev636@gmail.com']);
        die();

        $paymentMethods = $this->provider->getPaymentMethodsForCustomer("cus_MZ1xmXhZuuCSy7");
        print_r($paymentMethods);
        die();
    }

}