<?php

namespace apexl\Io\modules\payment\entities;

use apexl\Config\Singleton;
use apexl\encryption\Encrypt;
use apexl\Io\includes\Entity;
use apexl\Io\includes\Hook;
use apexl\Io\includes\Routes;
use apexl\Io\includes\System;
use apexl\Io\modules\invoice\entities\invoiceEntity;
use apexl\Io\modules\invoice\services\invoiceService;
use apexl\Io\modules\payment\entities\operators\paymentTokenOperator;
use apexl\Io\modules\payment\providers\stripeProvider;
use apexl\Io\modules\payment\services\cardValidationService;
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\services\currentUser;

class paymentTokenEntity extends Entity{

    protected $lastPayment=null;
    protected $cardValidationService;
    protected $createSubscription = false;

    public function __construct()
    {
        parent::__construct('payment_token');
        $this->setOperator(new paymentTokenOperator('payment_token'));
        $this->cardValidationService = new cardValidationService();
        $this->config = Singleton::getInstance();
    }

    /**
     * @param $providerSessionId
     * @return void
     * @throws \Exception
     */
    public function encryptProviderSession($providerSessionId){
        list($token, $sessionId) = $this->cardValidationService->getProviderTokenUsingSessionId($providerSessionId);

        if ($token) {
            $encryption = new Encrypt();
            list($this->token_iv, $this->token) = $encryption->encrypt($token, $this->config->app->encryption->key);
            list($this->payment_method_ref_iv, $this->payment_method_ref) = $encryption->encrypt($sessionId, $this->config->app->encryption->key);
            $this->validated = 1;
        }

        $this->modified = time();
        $this->store();
    }

    /**
     * Validate the provided token
     * @param $request
     * @param array $validationMeta
     * @param $token
     * @return $this
     * @throws \Exception
     */
    public function validateToken($request, array $validationMeta = [], $token = NULL){
        if($token){
            $this->load($token);
        }
        if(!$this->id){
            throw new \Exception($token." || No token to validate");
        }
        if(!($validationMeta['user'] ?? FALSE)){
            $validationMeta['user'] = currentUser::getCurrentUser();
            if(!$validationMeta['user']){
                throw new \Exception("No user available");
            }
        }

        if (!$this->setup_complete && $this->validated) {
            if(is_array($this->data)){
                $data = json_decode($this->data['data']);
            } else {
                $data = json_decode($this->data);
            }
            $meta = (array)($data->meta ?? []);

            $this->createSubscription = $validationMeta['createSubscription'] = (!isset($data->create_subscription) || $data->create_subscription) && !isset($data->subscriptionCode);
            $validationMeta['charge_immediately'] = (isset($data->total) && $data->total > 0 && !isset($data->subscriptionCode)) || (isset($data->localInvoiceId) && $data->localInvoiceId > 0);// We don't want to create a new subscription if the user is adding/updating a payment method for/to an existing subscription
            if (isset($data->startDate)) $validationMeta['startDate'] = $data->startDate;
            if (isset($data->gracePeriodEnd)) $validationMeta['gracePeriodEnd'] = $data->gracePeriodEnd;
            if (isset($data->nextBillingDate)) $validationMeta['nextBillingDate'] = $data->nextBillingDate;
            $validationMeta = Hook::processHook('pre_validate_payment_token', $validationMeta, $this);

            if (isset($data->userFirstName) && $data->userFirstName && isset($data->userLastName) && $data->userLastName) {
                $niceName = $data->userFirstName . ' ' . $data->userLastName;
            } else $niceName = $validationMeta['user']->getNiceName();
            $validationMeta['user'] = $this->cardValidationService->completeValidation($request, $this, $validationMeta['user']->email, $niceName, $validationMeta['userPassword'] ?? null, $validationMeta['providerSessionId'] ?? null, $meta);
            if ($this->setup_complete == '1') {
                if ($validationMeta['createSubscription']) {
                $subscriptionService = new subscriptionService();
                $createdSubscriptions = $subscriptionService->createSubscriptionsWithToken($validationMeta['user'], $this, $validationMeta['startDate'] ?? NULL, $validationMeta['gracePeriodEnd'] ?? NULL, $validationMeta['nextBillingDate'] ?? NULL);
                    //if ((!isset($validationMeta['nextBillingDate']) || is_null($validationMeta['nextBillingDate'])) && isset($this->config->app->payments->subscriptions->rollForwardOnInitialPayment) && $this->config->app->payments->subscriptions->rollForwardOnInitialPayment == '1') {
                    //    $subscriptionService->rollForwardSubscriptions($createdSubscriptions);
                    //}
                    Hook::processHook('validation_post_subscription_creation', $createdSubscriptions, $data, $this, $validationMeta['providerSessionId'] ?? null);

                } elseif (isset($data->subscriptionCode) || isset($data->addPaymentMethod) && $data->addPaymentMethod) {
                    $subscriptionService = new subscriptionService();
                    //$subscriptionService->reenableUserSubscriptions($validationMeta['user']);
                    $invoiceService = new invoiceService();
                    $invoiceService->reenableUserInvoices($validationMeta['user']->id);
                    Hook::processHook('post_add_payment_method', $this, $validationMeta['user']);
                }
            }
            $validationMeta['user'] = Hook::processHook('post_validate_payment_token', $validationMeta['user'], $this->cardValidationService);
            if ($this->setup_complete == '1') {
                $this->removeDefaultForUser($this->user_id);
                $this->default_token = 1;
                $this->store();
                if (isset($createdSubscriptions) && isset($data->productPriceOverride) && $data->productPriceOverride->price > 0) {
                    foreach ($createdSubscriptions as $subscription) {
                        if ($subscription instanceof subscriptionEntity) {
                            $subscription->product_price_override = $data->productPriceOverride->price;
                            $subscription->product_price_override_ends = $data->productPriceOverride->ends;
                            $subscription->store();
                        }
                    }
                }
            }
            if($validationMeta['charge_immediately']) {
                $this->chargeToken($validationMeta['user'], !isset($data->subscriptionCode), TRUE, $createdSubscriptions ?? [], (!isset($validationMeta['nextBillingDate']) || is_null($validationMeta['nextBillingDate'])), $data->localInvoiceId ?? null);
            }

            if ($paymentPublicId = $this->is3DSecureRequired()) { // Redirect the user for 3D secure
                System::redirect($this->config->app->site->frontend_domain. Routes::getRoutePattern('payment.display.threedsecure', ['publicId' => $paymentPublicId]));
            }

            if($data->order_complete_url ?? FALSE){ //redirect the user if we have an external order id
                $subscriptions = $createdSubscriptions ?? [];
                $replacements = [
                    'SUBSCRIPTION_ID' => $subscriptions[0]->id ?? ""
                ];
                foreach ($replacements as $find => $replace) {
                    $data->order_complete_url = str_replace("{".$find."}", $replace, $data->order_complete_url);
                }
                System::redirect($data->order_complete_url);
            }
        }
        //no redirect?
        return $this;
    }

    /**
     * Charge this token.
     * @param $user
     * @param bool $enableAnySubscription
     * @return $this
     */
    public function chargeToken($user, bool $enableAnySubscription = FALSE, $userPresent = FALSE, $subscriptions, $rollForwardSubscriptions = TRUE, $localInvoiceId = null)
    {
        if ($this->setup_complete && $this->validated) {
            if(is_array($this->data)){
                $data = json_decode($this->data['data']);
            } else {
                $data = json_decode($this->data);
            }

            $invoiceService = new invoiceService();
            $meta = (array)($data->meta ?? []);

            if ($localInvoiceId) { // invoice id provided within payment link - don't create a new invoice - bill existing one
                $invoiceEntity = new invoiceEntity();
                $invoiceEntity->load($localInvoiceId);

                $data = Hook::processHook('pre_charge_payment_token', $data, $this);
                $this->lastPayment = $invoiceService->attemptInvoicePayment($invoiceEntity, 'InvId-' . $invoiceEntity->id, true, $data, $meta, $userPresent);
                Hook::processHook('post_charge_payment_token', $this->lastPayment, $this);

                if ($this->lastPayment && $this->lastPayment->status == 'successful') {
                    $invoiceEntity->paid = 1;
                    $invoiceEntity->store();
                }

            } else { // Create a new invoice, either user present or not

            $lineItems = [];

            $productId = $subscriptions[0]->product_id ?? null;
            $name = "Fee paid on setup";
            $nominalCode = '200';
            if ($productId) {
                $productEntity = new productEntity();
                $productEntity->load($productId);
                $name = trim($productEntity->product_name ?? $name);
                $nominalCode = $productEntity->nominal_code ?? $nominalCode;
            }
            $lineItems[] = $invoiceService->createInvoiceLineItem($name, 1, $data->total, $nominalCode, $productId);
            $lineItems = Hook::processHook('pre_charge_payment_token_create_invoice', $lineItems, $data);

            $paymentProvider = null;
            if (isset($this->config->app->payments->subscriptions->defaultPaymentProvider) && $this->config->app->payments->subscriptions->defaultPaymentProvider != '') {
                $paymentProvider = $this->config->app->payments->subscriptions->defaultPaymentProvider;
            }

            if ($userPresent) { // If user is present, do not submit to invoice provider just yet
                $invoiceEntity = $invoiceService->createInvoice($user->id, $lineItems, false, $subscriptions, $paymentProvider, ($data->order_id ?? null), $rollForwardSubscriptions, true, null, 'email', FALSE, 'Exclusive', null, false);

                $data = Hook::processHook('pre_charge_payment_token', $data, $this);
                $this->lastPayment = $invoiceService->attemptInvoicePayment($invoiceEntity, 'InvId-'.$invoiceEntity->id, true, $data, $meta, $userPresent);
                Hook::processHook('post_charge_payment_token', $this->lastPayment, $this);

                if ($this->lastPayment && $this->lastPayment->status == 'successful') {
                    $invoiceEntity->send_to_provider = 1;
                    $invoiceEntity->paid = 1;
                    $invoiceEntity->store();
                    $invoiceService->sendInvoiceToProvider($invoiceEntity->id);
                } else {
                    foreach ($subscriptions as $subscription) { // Disable the subscriptions
                        $subscription->enabled = 0;
                        $subscription->store();
                    }
                    if (!$this->lastPayment || $this->lastPayment->status != 'three_d_secure_required') {
                        $invoiceEntity->abandoned = 1;
                        $invoiceEntity->store();
                    } elseif ($this->lastPayment->status == 'three_d_secure_required' && trim($invoiceEntity->remote_order_id) != "") {
                        $invoiceEntity->billing_enabled = 0; // Don't allow rebilling of invoices if its for an order,  as it should complete straight away
                        $invoiceEntity->store();
                    }
                }


            } else {
                // Create invoice AND send to provider BEFORE billing the customer
                $invoiceEntity = $invoiceService->createInvoice($user->id, $lineItems, false, $subscriptions, $paymentProvider, ($data->order_id ?? null), $rollForwardSubscriptions);

                $data = Hook::processHook('pre_charge_payment_token', $data, $this);
                $this->lastPayment = $invoiceService->attemptInvoicePayment($invoiceEntity, 'InvId-'.$invoiceEntity->id, true, $data, $meta, $userPresent);
                Hook::processHook('post_charge_payment_token', $this->lastPayment, $this);
                if (isset($data->subscriptionCode) && $enableAnySubscription) {
                    $subscriptionService = new subscriptionService();
                    $subscriptionService->reenableUserSubscriptions($user);
                    $invoiceService->reenableUserInvoices($user->id);
                }
            }
            }
        }
        return $this;
    }

    public function is3DSecureRequired()
    {
        return (isset($this->lastPayment->status) && $this->lastPayment->status == 'three_d_secure_required') ? $this->lastPayment->public_id : false;
    }
    public function getLastPayment()
    {
        return $this->lastPayment;
    }
}