<?php

namespace WeDevs\DokanPro\Modules\PayPalMarketplace\PaymentMethods;

use WC_Order_Item_Product;
use WC_Payment_Gateway;
use WeDevs\DokanPro\Modules\PayPalMarketplace\Cart\CartManager;
use WeDevs\DokanPro\Modules\PayPalMarketplace\Helper;
use WeDevs\DokanPro\Modules\PayPalMarketplace\Order\OrderManager;
use WeDevs\DokanPro\Modules\PayPalMarketplace\Utilities\Processor;
use WeDevs\DokanPro\Modules\PayPalMarketplace\WebhookHandler;

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly
}

/**
 * Class PayPal
 * @package WeDevs\DokanPro\Modules\PayPalMarketplace\PaymentMethods
 *
 * @since 3.3.0
 */
class PayPal extends WC_Payment_Gateway {

    /**
     * Test mode flag
     *
     * @since 3.3.0
     *
     * @var string
     */
    public $test_mode;

    /**
     * App user
     *
     * @since 3.3.0
     *
     * @var string
     */
    public $app_user;

    /**
     * App password
     *
     * @since 3.3.0
     *
     * @var string
     */
    public $app_pass;

    /**
     * App id
     *
     * @since 3.3.0
     *
     * @var string
     */
    public $app_id;

    /**
     * Test app user
     *
     * @since 3.3.0
     *
     * @var string
     */
    public $test_app_user;

    /**
     * Test app password
     *
     * @since 3.3.0
     *
     * @var string
     */
    public $test_app_pass;

    /**
     * Debug flag
     *
     * @since 3.3.0
     *
     * @var string
     */
    public $debug;

    /**
     * Constructor for the gateway.
     *
     * @since 3.3.0
     *
     * @return void
     */
    public function __construct() {
        $this->supports = [
            'products',
            'refunds',
            'subscriptions',
        ];

        $this->init_fields();

        // Load the settings.
        $this->init_form_fields();
        $this->init_settings();

        $this->init_hooks();

        if ( ! $this->is_valid_for_use() ) {
            $this->enabled = 'no';
        }
    }

    /**
     * Init essential fields
     *
     * @since 3.3.0
     *
     * @return void
     */
    public function init_fields() {
        $this->id                 = Helper::get_gateway_id();
        $this->has_fields         = true;
        $this->method_title       = __( 'Dokan PayPal Marketplace', 'dokan' );
        $this->method_description = __( 'Pay Via PayPal Marketplace', 'dokan' );
        $this->icon               = apply_filters( 'woocommerce_paypal_icon', DOKAN_PAYPAL_MP_ASSETS . 'images/paypal-marketplace.svg' );

        $title                = $this->get_option( 'title' );
        $this->title          = empty( $title ) ? __( 'PayPal Marketplace', 'dokan' ) : $title;
        $this->test_mode      = $this->get_option( 'test_mode' );
        $this->app_user       = $this->get_option( 'app_user' );
        $this->app_pass       = $this->get_option( 'app_pass' );
        $this->app_id         = $this->get_option( 'app_id' );
        $this->test_app_user  = $this->get_option( 'test_app_user' );
        $this->test_app_pass  = $this->get_option( 'test_app_pass' );
        $this->debug          = $this->get_option( 'debug' );
    }

    /**
     * Initialise Gateway Settings Form Fields
     *
     * @since 3.3.0
     *
     * @return void
     */
    public function init_form_fields() {
        $this->form_fields = require DOKAN_PAYPAL_MP_TEMPLATE_PATH . 'admin-gateway-settings.php';
    }

    /**
     * Initialize necessary actions
     *
     * @since 3.3.0
     *
     * @return void
     */
    public function init_hooks() {
        add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, [ &$this, 'process_admin_options' ] );
        add_action( 'admin_footer', [ $this, 'admin_script' ] );
    }

    /**
     * Check if this gateway is enabled and available in the user's country
     *
     * @since 3.3.0
     *
     * @return bool
     */
    public function is_valid_for_use() {
        return array_key_exists( get_woocommerce_currency(), Helper::get_supported_currencies() );
    }

    /**
     * Display information in frontend
     * after checkout process button
     *
     * @since 3.3.0
     *
     * @return void
     */
    public function payment_fields() {
        echo $this->get_option( 'description' );

        $payment_fields = apply_filters( 'dokan_paypal_payment_fields', true );

        if ( $payment_fields && CartManager::is_ucc_enabled_for_all_seller_in_cart() ) {
            Helper::get_template( '3DS-payment-option' );
        }
    }

    /**
     * Process the payment and return the result
     *
     * @param int $order_id
     *
     * @since 3.3.0
     *
     * @return array
     */
    public function process_payment( $order_id ) {
        $order = wc_get_order( $order_id );

        $redirect_url = $this->get_return_url( $order );

        $paypal_order_id     = $order->get_meta( '_dokan_paypal_order_id' );
        $paypal_redirect_url = $order->get_meta( '_dokan_paypal_redirect_url' );

        /*
         * The stored fingerprint records the order state the sub orders — and any
         * PayPal order — were built from. A stored fingerprint that no longer matches
         * proves the order changed; a missing one proves nothing, so it must never be
         * read as "changed" and must never trigger anything destructive.
         */
        $stored_fingerprint  = $order->get_meta( '_dokan_paypal_order_fingerprint' );
        $fingerprint_matches = ! empty( $stored_fingerprint )
            && hash_equals( (string) $stored_fingerprint, $this->get_order_fingerprint( $order ) );
        $fingerprint_differs = ! empty( $stored_fingerprint ) && ! $fingerprint_matches;

        if ( $order->get_meta( '_paypal_payment_success' ) ) {
            $order->payment_complete();
        } elseif ( $paypal_order_id && $fingerprint_matches ) {
            // Reuse the PayPal order instead of leaving another orphaned order on PayPal's end.
            $redirect_url = $paypal_redirect_url ? $paypal_redirect_url : $redirect_url;
        } else {
            $paypal_order_id     = '';
            $paypal_redirect_url = '';

            // PayPal store orphaned order for 3 hours, so we don't need to check if multiple order created on paypal end.
            $process_payment = apply_filters( 'dokan_paypal_process_payment', [ 'order' => $order ] );

            if ( isset( $process_payment['product_type'] ) && 'product_pack' === $process_payment['product_type'] ) {
                return $process_payment['data'];
            }

            /*
             * Sub orders built from a previous state of the order are rebuilt only when
             * the order provably changed AND nothing has been captured against them yet.
             *
             * Rebuilding deletes the sub orders outright, and they carry the PayPal
             * capture id, the processing and platform fees, and the vendor earnings. So
             * anything already captured — including a capture that arrived through the
             * `CHECKOUT.ORDER.COMPLETED` webhook, which completes payment without
             * setting `_paypal_payment_success` — must be left alone. Otherwise a later
             * refund cannot find its capture id.
             */
            $has_been_split = wc_string_to_bool( $order->get_meta( 'has_sub_order' ) ) || (int) $order->get_meta( '_dokan_vendor_id' ) > 0;
            $is_captured    = wc_string_to_bool( $order->get_meta( '_dokan_paypal_payment_charge_captured' ) )
                || wc_string_to_bool( $order->get_meta( '_paypal_payment_success' ) )
                || $order->is_paid();

            $force_recreate_sub_orders = $has_been_split && $fingerprint_differs && ! $is_captured;

            if ( $force_recreate_sub_orders ) {
                // Lite's splitter skips any order that already has a vendor assigned, so
                // the single-vendor marker has to go for the re-split to run at all.
                $order->delete_meta_data( '_dokan_vendor_id' );
                $order->save();
            }

            if ( $force_recreate_sub_orders || ! $order->get_meta( 'has_sub_order' ) ) {
                dokan()->order->maybe_split_orders( $order_id, $force_recreate_sub_orders );
            }

            $sub_orders = dokan()->order->get_child_orders( $order_id );
            if ( ! count( $sub_orders ) ) {
                $sub_orders = [ $order ];
            }

            $purchase_units = [];

            foreach ( $sub_orders as $sub_order ) {
                $sub_order->update_meta_data( '_dokan_paypal_payment_disbursement_mode', Helper::get_disbursement_mode() );
                $sub_order->save();
                $purchase_units[] = OrderManager::make_purchase_unit_data( $sub_order );
            }

            /*
             * Record the order state the sub orders were built from before calling
             * PayPal, so that a failed create still leaves them verifiable and a
             * retry after a cart change knows to rebuild them.
             */
            $order->update_meta_data( '_dokan_paypal_order_fingerprint', $this->get_order_fingerprint( $order ) );
            $order->save();

            $create_order_data = [
                'intent'              => 'CAPTURE',
                'payer'               => OrderManager::get_shipping_address( $order, true ),
                'application_context' => [
                    'return_url'          => add_query_arg(
                        [
                            'button_type'       => Helper::get_settings( 'button_type' ),
                            'wc_payment_method' => Helper::get_gateway_id(),
                        ],
                        $this->get_return_url( $order )
                    ),
                    'cancel_url'          => $order->get_cancel_order_url_raw(),
                    'brand_name'          => get_bloginfo( 'name' ),
                    'user_action'         => 'PAY_NOW',
                    'payment_method'      => [
                        'payer_selected'  => 'PAYPAL',
                        'payee_preferred' => 'IMMEDIATE_PAYMENT_REQUIRED',
                    ],
                    'shipping_preference' => 'SET_PROVIDED_ADDRESS',
                ],
                'purchase_units'      => $purchase_units,
            ];

            $processor        = Processor::init();
            $create_order_url = $processor->create_order( $create_order_data );

            if ( is_wp_error( $create_order_url ) ) {
                $error_message = sprintf(
                // translators: 1) error message from payment gateway
                    __( 'Error while creating PayPal order: %1$s', 'dokan' ), Helper::get_error_message( $create_order_url )
                );
                // wc_add_notice( $error_message, 'error' );
                dokan_log( '[Dokan PayPal Marketplace] Create Order Data: ' . print_r( $create_order_data, true ) );
                Helper::log_paypal_error( $order->get_id(), $create_order_url, 'dpm_create_order' );

                return [
                    'result'   => 'failure',
                    'redirect' => false,
                    // The checkout block reads `message`, the classic AJAX flow reads `messages`.
                    'message'  => $error_message,
                    'messages' => '<ul class="woocommerce-error" role="alert"><li>' . $error_message . '</li></ul>',
                ];
            }
            //store paypal debug id & create order id
            $order->update_meta_data( '_dokan_paypal_create_order_debug_id', $create_order_url['paypal_debug_id'] );
            $order->update_meta_data( '_dokan_paypal_order_id', $create_order_url['id'] );
            $order->update_meta_data( '_dokan_paypal_redirect_url', $create_order_url['links'][1]['href'] );
            $order->update_meta_data( 'shipping_fee_recipient', 'seller' );
            $order->update_meta_data( 'tax_fee_recipient', 'seller' );
            $order->save();

            $paypal_order_id     = $create_order_url['id'];
            $paypal_redirect_url = $create_order_url['links'][1]['href'];
            // Update redirect URL for redirecting user to PayPal.
            $redirect_url = $paypal_redirect_url;
        }

        return [
            'result'              => 'success',
            'id'                  => $order_id,
            'paypal_redirect_url' => $paypal_redirect_url,
            'paypal_order_id'     => $paypal_order_id,
            'redirect'            => $redirect_url,
            'success_redirect'    => $this->get_return_url( $order ),
            'cancel_redirect'     => $order->get_cancel_order_url_raw(),
        ];
    }

    /**
     * Build a fingerprint of everything a PayPal order is created from.
     *
     * @since 5.0.8
     *
     * @param \WC_Order $order The order being paid for.
     *
     * @return string
     */
    protected function get_order_fingerprint( $order ) {
        $items = [];

        foreach ( $order->get_items() as $item ) {
            if ( ! $item instanceof WC_Order_Item_Product ) {
                continue;
            }

            $product_id = $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id();

            $items[ $product_id ] = [
                'product'  => $product_id,
                'quantity' => $item->get_quantity(),
                'total'    => wc_format_decimal( (float) $item->get_total() + (float) $item->get_total_tax(), wc_get_price_decimals() ),
            ];
        }

        // Line item ids change whenever the cart is synced back to the order, so key on the product instead.
        ksort( $items );

        $fingerprint_data = [
            'currency' => $order->get_currency(),
            'total'    => wc_format_decimal( $order->get_total(), wc_get_price_decimals() ),
            'items'    => array_values( $items ),
        ];

        /**
         * Filter the data a PayPal order is fingerprinted from.
         *
         * Anything included here forces a new PayPal order to be created when it changes.
         *
         * @since 5.0.8
         *
         * @param array     $fingerprint_data The data used to build the fingerprint.
         * @param \WC_Order $order            The order being paid for.
         */
        $fingerprint_data = apply_filters( 'dokan_paypal_marketplace_order_fingerprint_data', $fingerprint_data, $order );

        return md5( wp_json_encode( $fingerprint_data ) );
    }

    /**
     * Get the state to send to paypal
     *
     * @param string $cc
     * @param string $state
     *
     * @since 3.3.0
     *
     * @return string
     */
    public function get_paypal_state( $cc, $state ) {
        if ( 'US' === $cc ) {
            return $state;
        }

        $states = WC()->countries->get_states( $cc );

        if ( isset( $states[ $state ] ) ) {
            return $states[ $state ];
        }

        return $state;
    }

    /**
     * Admin options with extra information
     *
     * @since 3.3.0
     *
     * @return void
     */
    public function admin_options() {
        if ( $this->is_valid_for_use() ) {
            parent::admin_options();
        } else {
            ?>
            <div class="inline error">
                <p>
                    <strong><?php esc_html_e( 'Gateway disabled', 'dokan' ); ?></strong>:
                    <?php
                    echo wp_kses(
                        sprintf(
                        // translators: 1) UCC supported country lists
                            __( 'Dokan PayPal Marketplace does not support your store currency. <strong>Supported Currencies are:</strong> %1$s', 'dokan' ),
                            implode( ', ', Helper::get_supported_currencies() )
                        ),
                        [
                            'strong' => [],
                        ]
                    );
                    ?>
                </p>
            </div>
            <?php
        }
    }

    /**
     * Admin script
     *
     * @since 4.0.0_LITE
     *
     * @return void
     */
    public function admin_script() {
        ?>
        <script type="text/javascript">
            ;(function ($, document) {
                const Dokan_PayPal_MarketPlace_Settings = {
                    payment_id_prefix: 'woocommerce_<?php echo Helper::get_gateway_id(); ?>_',

                    init: function() {
                        Dokan_PayPal_MarketPlace_Settings.inputToggle();
                        //toggle sandbox and live api credentials
                        $(`#${Dokan_PayPal_MarketPlace_Settings.payment_id_prefix}test_mode`).on('change', Dokan_PayPal_MarketPlace_Settings.inputToggle);

                        Dokan_PayPal_MarketPlace_Settings.disbursementPeriodToggle();
                        // toggle disbursement mode
                        $(`#${Dokan_PayPal_MarketPlace_Settings.payment_id_prefix}disbursement_mode`).on('change', Dokan_PayPal_MarketPlace_Settings.disbursementPeriodToggle);

                        // validate disbursement period validation
                        Dokan_PayPal_MarketPlace_Settings.disbursementPeriodValidation();
                        $(`#${Dokan_PayPal_MarketPlace_Settings.payment_id_prefix}disbursement_delay_period`).on('change', Dokan_PayPal_MarketPlace_Settings.disbursementPeriodValidation);

                        Dokan_PayPal_MarketPlace_Settings.noticeIntervalToggle();
                        //toggle notice interval fields
                        $(`#${Dokan_PayPal_MarketPlace_Settings.payment_id_prefix}display_notice_to_non_connected_sellers`).on('change', Dokan_PayPal_MarketPlace_Settings.noticeIntervalToggle);
                    },

                    inputToggle: function() {
                        let settings_input_ids = [
                            'app_user',
                            'app_pass'
                        ];

                        if ( $(`#${Dokan_PayPal_MarketPlace_Settings.payment_id_prefix}test_mode`).is(':checked') ) {
                            settings_input_ids.map(function (id) {
                                $('#' + Dokan_PayPal_MarketPlace_Settings.payment_id_prefix + 'test_' + id).closest('tr').show();
                                $('#' + Dokan_PayPal_MarketPlace_Settings.payment_id_prefix + id).closest('tr').hide();
                            });
                        } else {
                            settings_input_ids.map(function (id) {
                                $('#' + Dokan_PayPal_MarketPlace_Settings.payment_id_prefix + id).closest('tr').show();
                                $('#' + Dokan_PayPal_MarketPlace_Settings.payment_id_prefix + 'test_' + id).closest('tr').hide();
                            });
                        }
                    },

                    disbursementPeriodToggle: function() {
                        let val = $(`#${Dokan_PayPal_MarketPlace_Settings.payment_id_prefix}disbursement_mode`).val();
                        if ( val === 'DELAYED' ) {
                            $(`#${Dokan_PayPal_MarketPlace_Settings.payment_id_prefix}disbursement_delay_period`).closest('tr').show();
                        } else {
                            $(`#${Dokan_PayPal_MarketPlace_Settings.payment_id_prefix}disbursement_delay_period`).closest('tr').hide();
                        }
                    },

                    disbursementPeriodValidation: function() {
                        let disbursementPeriod = $(`#${Dokan_PayPal_MarketPlace_Settings.payment_id_prefix}disbursement_delay_period`);
                        if ( parseInt( disbursementPeriod.val() ) > 29 ) {
                            disbursementPeriod.val( 29 );
                        }
                    },

                    noticeIntervalToggle: function() {
                        let noticeCheckbox = $(`#${Dokan_PayPal_MarketPlace_Settings.payment_id_prefix}display_notice_to_non_connected_sellers`);
                        let noticeInterval = $(`#${Dokan_PayPal_MarketPlace_Settings.payment_id_prefix}display_notice_interval`);
                        if ( noticeCheckbox.prop('checked') ) {
                            noticeInterval.closest('tr').show();
                        } else {
                            noticeInterval.closest('tr').hide();
                        }
                    },
                };

                $( document ).ready( function ( $ ) {
                    Dokan_PayPal_MarketPlace_Settings.init();
                } );

            })(jQuery, document);
        </script>
        <?php
    }

    /**
     * Process admin options
     *
     * @since 3.3.0
     *
     * @return bool|void
     */
    public function process_admin_options() {
        parent::process_admin_options();

        // delete token transient after settings is being updated
        delete_transient( '_dokan_paypal_marketplace_access_token' );
        delete_transient( '_dokan_paypal_marketplace_client_token' );

        //create webhook automatically
        /**
         * @uses \WeDevs\DokanPro\Modules\PayPalMarketplace\WebhookHandler $instance
         */
        $instance = dokan_pro()->module->paypal_marketplace->webhook;

        if ( ! $instance instanceof WebhookHandler ) {
            return;
        }

        if ( Helper::is_enabled() ) {
            //if gateway is enabled, automatically create webhook for this site
            $instance->register_webhook();
        } else {
            //if gateway is disabled, delete created webhook for this site
            $instance->deregister_webhook();
        }

        /*
         * hack applied to process vendor subscription payment,
         * from kapil bhai: Amra method e seller er id and details dei. Tai admin er tao evabe rakha
         */
        //todo: remove below lines from here
        //update_user_meta( dokan_get_current_user_id(), '_dokan_paypal_merchant_id', $this->get_option( 'partner_id' ) );
        //update_user_meta( dokan_get_current_user_id(), '_dokan_paypal_enable_for_receive_payment', true );
    }

    /**
     * Check if this payment method is available with conditions
     *
     * @since 3.3.0
     * @since 3.11.4 Added additional validation with cart item to determined that vendor can use this payment gateway or not.
     * @since 3.11.4 Added filter to modify the availability of the Stripe Express payment gateway.
     *
     * @return bool
     */
    public function is_available() {
        $is_available = parent::is_available() && Helper::is_ready() && Helper::validate_cart_items();

        /**
         * Filter to modify the availability of the Stripe Express payment gateway.
         *
         * @since 3.11.4
         *
         * @param bool $is_available Whether the gateway is available or not.
         */
        return apply_filters( 'dokan_paypal_marketplace_is_gateway_available', $is_available );
    }

    /**
     * Return whether or not this gateway still requires setup to function.
     *
     * When this gateway is toggled on via AJAX, if this returns true a
     * redirect will occur to the settings page instead.
     *
     * @since 3.3.0
     * @return bool
     */
    public function needs_setup(): bool {
        return empty( Helper::get_partner_id() )
               || ! Helper::is_api_ready();
    }
}

