<?php


namespace WPPayForm\App\Modules\PaymentMethods\Stripe;

use WPPayForm\App\Hooks\Handlers\SubmissionHandler;
use WPPayForm\App\Models\Form;
use WPPayForm\App\Models\Submission;
use WPPayForm\App\Models\SubmissionActivity;
use WPPayForm\App\Models\Subscription;
use WPPayForm\App\Models\Transaction;
use WPPayForm\App\Services\GeneralSettings;
use WPPayForm\Framework\Support\Arr;
use WPPayFormPro\Classes\PaymentsHelper;

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Handle Stripe Inline Card Payments
 * @since 1.3.0
 */
class StripeInlineHandler extends StripeHandler
{
    public $paymentMethod = 'stripe_inline';

    public function registerHooks()
    {
        /*
         * After form submission this hooks fire to start Making payment
         */
        add_filter('wppayform/form_submission_make_payment_stripe_inline', array($this, 'makePaymentIntent'), 10, 6);

        /*
         * Mainly for single payment items
         */
        add_action('wp_ajax_wppayform_sca_inline_confirm_payment', array($this, 'confirmScaPayment'));
        add_action('wp_ajax_nopriv_wppayform_sca_inline_confirm_payment', array($this, 'confirmScaPayment'));

        /*
         * For Subscription payment + maybe single payment items
         */
        add_action('wp_ajax_wppayform_sca_inline_confirm_payment_setup_intents', array($this, 'confirmScaSetupIntentsPayment'));
        add_action('wp_ajax_nopriv_wppayform_sca_inline_confirm_payment_setup_intents', array($this, 'confirmScaSetupIntentsPayment'));
    }

    /*
     *
     * Step: 1 - AJAX EndPoint Here
     * In this step, We are creating a payment intent
     * Steps:
     *     1. If $hasSubscriptions creating setupIntent and let frontend to handle
     *     2. IF Single Payment then we creating $intentArgs and let @handlePaymentItentCharge
     *        to handle the payment.
     *     3. We are not handling anything else here
     */
    public function makePaymentIntent($transactionId, $submissionId, $form_data, $form, $hasSubscriptions, $totalPayable = 0)
    {
        $paymentMethodElements = Form::getPaymentMethodElements($form->ID);
        $transactionModel = new Transaction();
        $transaction = $transactionModel->getTransaction($transactionId);

        $submissionModel = new Submission();
        $submission = $submissionModel->getSubmission($submissionId);

        $lineItems = (new StripeHostedHandler())->getLineItems($submission, $totalPayable);

        if (defined('WPPAYFORMHASPRO')) {
            $formPaymentSettings = (new PaymentsHelper())->getPaymentSettings($form->ID);
        }

        $stripeMetaData = $formPaymentSettings['stripe_meta_data'] ?? [];
        $hasTransaction = $transaction && $transaction->payment_total;

        if (!$hasTransaction && !$hasSubscriptions) {
            return;
        }

        if (isset($lineItems)) {
            $finalAmounts = 0;
            foreach ($lineItems as $item) {
                $finalAmounts += (intval($item['amount']) * intval($item['quantity']));
            }
            $finalAmounts && $transaction->payment_total = $finalAmounts;
        }
        if ($hasSubscriptions) {
            $this->handleSetupIntent($submission, $form_data, $totalPayable, $stripeMetaData);
        }
        $paymentMethodId = Arr::get($form_data, '__stripe_payment_method_id');

        /*
         * Step 1 Create the customer first
         */
        $customerArgs = $this->customerArguments($paymentMethodId, $form, $submission);
        $customer = Customer::createCustomer($customerArgs, $submission->form_id);
        $placeAholdOnStripe = Arr::get($paymentMethodElements, 'stripe_card_element.options.checkout_display_style.place_a_hold_on_stripe') == 'yes';
        $placeAholdOnStripeForMultiplePayment = Arr::get($paymentMethodElements, 'choose_payment_method.options.method_settings.payment_settings.stripe.checkout_display_style.place_a_hold_on_stripe') == 'yes';
        $captureAfter = false;
        // Let's create the one time payment first
        // We will handle One-Time Payment Here only
        $intentArgs = [
            'payment_method' => $paymentMethodId,
            'amount' => $transaction->payment_total,
            'currency' => $transaction->currency,
            'confirmation_method' => 'manual',
            'confirm' => 'true',
            'description' => $form->post_title,
            'metadata' => $this->getIntentMetaData($submission, $stripeMetaData),
            'customer' => $customer->id
        ];

        if ($placeAholdOnStripe || $placeAholdOnStripeForMultiplePayment) {
            $intentArgs['capture_method'] = 'manual';
            $captureAfter = true;
        }

        $this->handlePaymentIntentCharge($transaction, $submission, $intentArgs);

        return true;
    }

    public function handleSetupIntent($submission, $formData, $totalPayable, $stripeMetaData)
    {
        $paymentMethodId = Arr::get($formData, '__stripe_payment_method_id');

        $form = Form::getForm($submission->form_id);

        /*
         * Step 1 Create the customer first
         */
        $customerArgs = $this->customerArguments($paymentMethodId, $form, $submission);
        $customer = Customer::createCustomer($customerArgs, $submission->form_id);

        $transaction = (new Transaction())->getLatestTransaction($submission->id);

        if (is_wp_error($customer)) {
            $this->handlePaymentChargeError($customer->get_error_message(), $submission, $transaction, $form, false, 'customer');
        }
        if ($transaction && $transaction->payment_total) {
            Invoice::createItem([
                'currency' => $transaction->currency,
                'customer' => $customer->id,
                'amount' => $transaction->payment_total,
                'description' => $form->post_title
            ], $submission->form_id);
        }

        $subscriptionPlans = (new StripeHandler())->getSubmissionPlans($submission, $totalPayable);

        if ($subscriptionPlans && is_wp_error($subscriptionPlans)) {
           return wp_send_json_error([
                'message' => $subscriptionPlans->get_error_message()
            ], 423);
        }

        $items = [];
        foreach ($subscriptionPlans as $subscriptionPlan) {
            $items[] = [
                'plan' => $subscriptionPlan['plan_id'],
                'quantity' => $subscriptionPlan['quantity'],
                'metadata' => [
                    'submission_id' => $submission->id
                ]
            ];
        }

        $subscriptionArgs = [
            'customer' => $customer->id,
            'items' => $items,
            'metadata' => (new StripeHostedHandler())->getIntentMetaData($submission, $stripeMetaData)
        ];

        if (count($subscriptionPlans) == 1) {
            $plan = $subscriptionPlans[0];
            if (!empty($plan['trial_expiration_at'])) {
                $subscriptionArgs['trial_end'] = $plan['trial_expiration_at'];
            }
            if (!empty($plan['subscription_cancel_at'])) {
                $subscriptionArgs['cancel_at'] = $plan['subscription_cancel_at'];
            }
        }

        $stripeSubscription = PlanSubscription::subscribe($subscriptionArgs);


        if (is_wp_error($stripeSubscription)) {
            $this->handlePaymentChargeError($stripeSubscription->get_error_message(), $submission, $transaction, $form, false, 'subscription');
        }

        $invoice = Invoice::retrive($stripeSubscription->latest_invoice, [
            'expand' => ['payment_intent']
        ], $submission->form_id);

        if (is_wp_error($invoice)) {
            $this->handlePaymentChargeError($invoice->get_error_message(), $submission, $transaction, $form, false, 'invoice');
        }
        
        $formDataRaw = $submission->form_data_raw;
        $formDataRaw['paypal_ipn_data'] = $invoice;
        $submissionModel = new Submission();

        $submissionModel->updateSubmission($submission->id, [
            'form_data_raw' => maybe_serialize($formDataRaw)
        ]);

        if ($invoice->payment_intent && $invoice->payment_intent->status == 'requires_action' &&
            $invoice->payment_intent->next_action->type == 'use_stripe_sdk') {
            // We need to factor authentication now
            wp_send_json_success([
                'stripe_subscription_id' => $stripeSubscription->id,
                'payment_method_id' => $paymentMethodId,
                'call_next_method' => 'stripeSetupItent',
                'intent' => $invoice->payment_intent,
                'submission_id' => $submission->id,
                'customer_name' => $submission->customer_name,
                'customer_email' => $submission->customer_email,
                'client_secret' => $invoice->payment_intent->client_secret,
                'message' => __('Verifying your card details. Please wait...', 'wp-payment-form')
            ], 200);
        }
        // now this payment is successful. We don't need anything else
        $this->handlePaidSubscriptionInvoice($invoice, $submission);
    }

    public function customerArguments($paymentMethodId, $form, $submission)
    {
        $customerArgs = [
            'payment_method' => $paymentMethodId,
            'invoice_settings' => [
                'default_payment_method' => $paymentMethodId
            ],
            'metadata' => [
                'payform_id' => $submission->form_id,
                'submission_id' => $submission->id,
                'form_name' => wp_strip_all_tags($form->post_title)
            ]
        ];
        if ($submission->customer_email) {
            $customerArgs['email'] = $submission->customer_email;
            $customerArgs['description'] = $submission->customer_email;
        }
        if ($submission->customer_name) {
            $customerArgs['name'] = $submission->customer_name;
            $customerArgs['description'] = $submission->customer_name;
        }

        return $customerArgs;
    }

    /*
     * This is the next call for handleSetupIntent
     * */
    public function confirmScaSetupIntentsPayment()
    {
        if (!isset($_REQUEST['wpf_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['wpf_nonce'])), 'wpf_form_submission')) {
            wp_send_json_error(array(
                'message' => __('Security verification failed. Please refresh and try again.', 'wp-payment-form'),
            ), 403);
        }

        if(isset($_REQUEST['submission_id'])){
            $submissionId = intval(sanitize_text_field(wp_unslash($_REQUEST['submission_id'])));
        }
        if(isset($_REQUEST['payment_intent_id'])) {
            $intentId = sanitize_text_field(wp_unslash($_REQUEST['payment_intent_id']));
        }
        $submissionModel = new Submission();
        $submission = $submissionModel->getSubmission($submissionId);

        do_action('wppayform/form_submission_activity_start', $submission->form_id);

        // Let's retrive the intent
        $intent = SCA::retrivePaymentIntent($intentId, [
            'expand' => [
                'invoice.payment_intent'
            ]
            ], $submission->form_id);

        if (is_wp_error($intent)) {
            $form = Form::getForm($submission->form_id);
            $this->handlePaymentChargeError($intent->get_error_message(), $submission, false, $form, false, 'payment_intent');
        }

        $invoice = $intent->invoice;
        $this->handlePaidSubscriptionInvoice($invoice, $submission, $intent);
    }

    public function handlePaidSubscriptionInvoice($invoice, $submission, $intent = null)
    {
        if ($invoice->status != 'paid') {
            wp_send_json_error([
                'message' => __('Payment Failed! Please try again', 'wp-payment-form')
            ], 423);
        }

        // Submission status as paid
        $submissionModel = new Submission();
        
        $submissionModel->updateSubmission($submission->id, [
            'payment_status' => 'paid',
            'payment_mode' => ($invoice->livemode) ? 'live' : 'test',
            'customer_id' => $invoice->customer,
            'updated_at' => current_time('mysql')
        ]);

        $subscriptionModel = new Subscription();
        $subscriptions = $subscriptionModel->getSubscriptions($submission->id);

        $paymentSuccessHandler = new PaymentSuccessHandler();
        $paymentSuccessHandler->processSubscriptionsSuccess($subscriptions, $invoice, $submission);

        $transactionModel = new Transaction();
        $transaction = $transactionModel->getLatestTransaction($submission->id);

        if ($transaction) {
            $paymentSuccessHandler->processOnetimeSuccess($transaction, $invoice, $submission);
        }

        $submissionModel->updateMeta($submission->id, 'stripe_checkout_hooked_fired', 'yes');

        $paymentSuccessFired = false;
        if ($transaction) {
            $transaction = $transactionModel->getTransaction($transaction->id);
            do_action('wppayform/form_payment_success_stripe', $submission, $transaction, $submission->form_id, $invoice);
            do_action('wppayform/form_payment_success', $submission, $transaction, $submission->form_id, $invoice);
            $paymentSuccessFired = true;
        }

        if ($subscriptions) {
            $subscriptions = $subscriptionModel->getSubscriptions($submission->id);
            do_action('wppayform/form_recurring_subscribed_stripe', $submission, $subscriptions, $submission->form_id);
            do_action('wppayform/form_recurring_subscribed', $submission, $subscriptions, $submission->form_id);

            if (!$paymentSuccessFired) {
                do_action('wppayform/form_payment_success_stripe', $submission, false, $submission->form_id, $invoice);
                do_action('wppayform/form_payment_success', $submission, false, $submission->form_id, $invoice);
            }
        }

        // do_action('wppayform/after_form_submission_complete', $submission, $submission->form_id);

        $formHandler = new SubmissionHandler();
        $formHandler->sendSubmissionConfirmation($submission, $submission->form_id);
    }

    public function getFirstTimePaymentTotal($submission)
    {
        $subscriptions = (new Subscription())->getSubscriptions($submission->id);
        $transaction = (new Transaction())->getLatestTransaction($submission->id);

        $paymentTotal = 0;
        foreach ($subscriptions as $subscription) {
            if ($subscription->trial_days) {
                continue;
            }
            if ($submission->initial_amount) {
                $paymentTotal += $subscription->initial_amount * $subscription->quantity;
            } else {
                $paymentTotal += $subscription->recurring_amount * $subscription->quantity;
            }
        }
        if ($transaction) {
            $paymentTotal += $transaction->payment_total;
        }

        return $paymentTotal;
    }

    /*
     * AJAX EndPoint Here
     * Specially for only one time payment form
     * This function call via ajax if the form only has single payment option and requires SCA
     * This is mainly 1.2
     * Steps:
     *      1. Client (initStripeSCAModal) send us PM and intentID we use that to confirm the payment
     *      2. if confirmation succeeded then call handlePaymentSuccess() to make the submission as paid
     *      3. if fails we let the client that, We are sorry, really sorry
     *      4. Finally send confirmation message easily
     */
    public function confirmScaPayment()
    {
        if (!isset($_REQUEST['wpf_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['wpf_nonce'])), 'wpf_form_submission')) {
            wp_send_json_error(array(
                'message' => __('Security verification failed. Please refresh and try again.', 'wp-payment-form'),
            ), 403);
        }

        if (isset($_REQUEST['submission_id'])) {
            $submissionId = absint(wp_unslash($_REQUEST['submission_id']));
        }
        if(isset($_REQUEST['payment_method'])){
            $paymentMethod = sanitize_text_field(wp_unslash($_REQUEST['payment_method']));
        }
        $submissionModel = new Submission();
        $submission = $submissionModel->getSubmission($submissionId);
        $transactionModel = new Transaction();
        if(isset($_REQUEST['payment_intent_id'])) {
            $paymentIntentId = sanitize_text_field(wp_unslash($_REQUEST['payment_intent_id']));
        }
        $transaction = $transactionModel->getLatestTransaction($submissionId);

        do_action('wppayform/form_submission_activity_start', $submission->form_id);

        $confirmation = SCA::confirmPayment($paymentIntentId, [
            'payment_method' => $paymentMethod
        ], $submission->form_id);

        if (empty($confirmation->error) && $confirmation->status == 'succeeded') {
            $this->handlePaymentSuccess($confirmation, $transaction, $submission, 'confirmation');
        } else {
            $form = Form::getForm($submission->form_id);
            $message = 'Payment has been failed. ' . $confirmation->error->message;
            $this->handlePaymentChargeError($message, $submission, $transaction, $form, $confirmation, 'payment_error');
            return;
        }

        $submissionModel = new Submission();
        $submission = $submissionModel->getSubmission($submission->id);
        $transaction = $transactionModel->getTransaction($transaction->id);

        do_action('wppayform/form_payment_success_stripe', $submission, $transaction, $submission->form_id, $confirmation, 'confirmation');
        do_action('wppayform/form_payment_success', $submission, $transaction, $submission->form_id, $confirmation, 'confirmation');

        $formHandler = new SubmissionHandler();
        $formHandler->sendSubmissionConfirmation($submission, $submission->form_id);
    }

    /*
     * Step 2 for Single Payment Form
     * Step 3.1 for subscrion payment's single amount
     *
     * Steps:
     *     1. Create createPaymentIntent for SCA
     *     2. if requires 'requires_action' then response to client to open initStripeSCAModal
     *     3. if payment success then call handlePaymentSuccess to make the payment as paid
     *     4. If failed to createPaymentIntent then send error response
     */
    private function handlePaymentIntentCharge($transaction, $submission, $intentArgs)
    {
        if ($submission->customer_email && apply_filters('wppayform/send_receipt_email', true, $submission)) {
            $intentArgs['receipt_email'] = $submission->customer_email;
        }

        $intent = SCA::createPaymentIntent($intentArgs, $submission->form_id);
         // This hook will be usefull for the developers to do something after the payment is processed
         do_action('wppayform/form_payment_processed', $submission->form_id, $submission, $intent, $intent->status);

        if (is_wp_error($intent)) {
            $form = Form::getForm($submission->form_id);
            $this->handlePaymentChargeError($intent->get_error_message(), $submission, $transaction, $form, false, 'payment_intent');
        }
        if ($intent->status =='requires_capture') {
            $transactionModel = new Transaction();
            
            $transactionModel->updateTransaction($transaction->id, array(
                    'status' => 'authorized',
                    'charge_id' => $intent->id,
                    'payment_mode' => $this->getMode($submission->form_id)
            ));


            // Get the current timestamp
            $currentTimestamp = time();

            // Calculate the timestamp for 7 days later
            $expiringAt = strtotime('+7 days', $currentTimestamp);            
            $paymentMode = $this->getMode($form->ID);
            do_action('wppayform/form_payment_stripe_authorized',$submission, $submission->form_id, $transaction, 'stripe');

            $submissionModel = new Submission();

            if ($transaction) {
                $transactionModel = new Transaction();
                $transactionModel->updateTransaction($transaction->id, array(
                    'status' => 'authorized',
                    'payment_method' => 'stripe',
                    'payment_mode' => $paymentMode,
                ));
            }

            $submissionModel->updateMeta($submission->id, 'customer_payment_method', 'card');
            $submissionModel->updateMeta($submission->id, 'payment_expiring_at', $expiringAt);

            $submissionModel->updateSubmission($submission->id, array(
                'payment_status' => 'authorized',
                'payment_method' => 'stripe',
                'payment_mode' => $paymentMode,
            ));

            SubmissionActivity::createActivity(array(
                'form_id' => $submission->form_id,
                'submission_id' => $submission->id,
                'type' => 'activity',
                'created_by' => 'Paymattic BOT',
                'content' => __('Payment is authorized. need to capture it before the expiration, payment status changes from pending to authorized and customer made transaction with: card', 'wp-payment-form')
            ));
        }
        else if ($intent->status == 'requires_action' &&
            $intent->next_action &&
            $intent->next_action->type == 'use_stripe_sdk') {
            # Tell the client to handle the action
            $transactionModel = new Transaction();
            
            $transactionModel->updateTransaction($transaction->id, array(
                    'status' => 'intended',
                    'charge_id' => $intent->id,
                    'payment_mode' => $this->getMode($submission->form_id)
            ));

            SubmissionActivity::createActivity(array(
                'form_id' => $submission->form_id,
                'submission_id' => $submission->id,
                'type' => 'activity',
                'created_by' => 'Paymattic BOT',
                'content' => __('SCA is required for this payment. Payment status changed from pending to intended', 'wp-payment-form')
            ));

            SubmissionActivity::createActivity(array(
                'form_id' => $submission->form_id,
                'submission_id' => $submission->id,
                'type' => 'activity',
                'created_by' => 'Paymattic BOT',
                'content' => __('SCA is required for this payment. Requested SCA info from customer', 'wp-payment-form')
            ));

            wp_send_json_success([
                'call_next_method' => 'initStripeSCAModal',
                'submission_id' => $submission->id,
                'stripe_payment_intent_client_secret' => $intent->client_secret,
                'message' => __('Strong Customer Authentication is required. Please complete 2 factor authentication.', 'wp-payment-form')
            ], 200);
        } elseif ($intent->status == 'succeeded') {
            // Payment is succcess here
            return $this->handlePaymentSuccess($intent, $transaction, $submission);
        } else {
            $message = __('Payment Failed! Your card may declined', 'wp-payment-form');
            if (!empty($intent->error->message)) {
                $message = $intent->error->message;
            }
            $form = Form::getForm($submission->form_id);
            $this->handlePaymentChargeError($message, $submission, $transaction, $form, false, 'payment_intent');
        }
    }

    /*
     * Step Final: if there has any single payment item exists
     *
     * Steps:
     *     1. Mae the single transaction and paid
     *     2. Make the submission as paid
     *     3. Fire wppayform/form_payment_success hook which is very important for
     *        email notifications and other future integrations
     */
    public function handlePaymentSuccess($intend, $transaction, $submission, $type = 'intend')
    {
        $charge = $intend->charges->data[0];

        $paymentMode = $this->getMode($submission->form_id);
        $transactionModel = new Transaction();
        $transactionModel->updateTransaction($transaction->id, array(
            'status' => 'paid',
            'charge_id' => $charge->id,
            'payment_method' => 'stripe',
            'payment_mode' => $paymentMode,
        ));

        $formDataRaw = $submission->form_data_raw;
        $formDataRaw['stripe_ipn_data'] = $intend;

        $submissionUpdateData = array(
            'payment_status' => 'paid',
            'payment_method' => 'stripe',
            'payment_mode' => $paymentMode,
            'form_data_raw' => maybe_serialize($formDataRaw)
        );
        $submissionModel = new Submission();
        $submissionModel->updateSubmission($submission->id, $submissionUpdateData);

        SubmissionActivity::createActivity(array(
            'form_id' => $submission->form_id,
            'submission_id' => $submission->id,
            'type' => 'activity',
            'created_by' => 'Paymattic BOT',
            'content' => __('One time Payment Successfully made via stripe. Charge ID: ', 'wp-payment-form') . $charge->id
        ));

        SubmissionActivity::createActivity(array(
            'form_id' => $submission->form_id,
            'submission_id' => $submission->id,
            'type' => 'activity',
            'created_by' => 'Paymattic BOT',
            'content' => __('Payment status changed from pending to paid', 'wp-payment-form')
        ));

        // Refresh submission and transaction from DB so hooks receive updated payment_status
        $submission = $submissionModel->getSubmission($submission->id);
        $transaction = $transactionModel->getTransaction($transaction->id);

        do_action('wppayform/form_payment_success', $submission, $transaction, $submission->form_id, false);
        do_action('wppayform/form_payment_success_stripe', $submission, $transaction, $submission->form_id, false);
        
        return true;
    }


    private function getIntentMetaData($submission, $stripeMetaData)
    {
        $metadata = [
            'Submission ID' => $submission->id,
            'Form ID' => $submission->form_id
        ];
        if ($submission->customer_email) {
            $metadata['customer_email'] = $submission->customer_email;
        }
        if ($submission->customer_name) {
            $metadata['customer_name'] = $submission->customer_name;
        }

        $metadata = array_merge($metadata, (new StripeHostedHandler())->getCustomMetaData($stripeMetaData, $submission));

        return apply_filters('wppayform/stripe_onetime_payment_metadata', $metadata, $submission);
    }
}
