import ContextHandlerFactory from "./Context/ContextHandlerFactory";
import {createAppleErrors} from "./Helper/applePayError";
import {setVisible} from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
import {setEnabled} from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler';
import FormValidator from "../../../ppcp-button/resources/js/modules/Helper/FormValidator";
import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler';
import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder";

class ApplepayButton {

    constructor(context, externalHandler, buttonConfig, ppcpConfig) {
        this.isInitialized = false;

        this.context = context;
        this.externalHandler = externalHandler;
        this.buttonConfig = buttonConfig;
        this.ppcpConfig = ppcpConfig;
        this.paymentsClient = null;
        this.formData = null;

        this.contextHandler = ContextHandlerFactory.create(
            this.context,
            this.buttonConfig,
            this.ppcpConfig
        );

        this.updatedContactInfo = []
        this.selectedShippingMethod = []
        this.nonce = document.getElementById('woocommerce-process-checkout-nonce')?.value || buttonConfig.nonce

        // Stores initialization data sent to the button.
        this.initialPaymentRequest = null;

        this.log = function() {
            if ( this.buttonConfig.is_debug ) {
                console.log('[ApplePayButton]', ...arguments);
            }
        }

        this.refreshContextData();

        // Debug helpers
        jQuery(document).on('ppcp-applepay-debug', () => {
            console.log('ApplePayButton', this.context, this);
        });
        document.ppcpApplepayButtons = document.ppcpApplepayButtons || {};
        document.ppcpApplepayButtons[this.context] = this;
    }

    init(config) {
        if (this.isInitialized) {
            return;
        }

        if (!this.contextHandler.validateContext()) {
            return;
        }

        this.log('Init', this.context);
        this.initEventHandlers();
        this.isInitialized = true;
        this.applePayConfig = config;
        const isEligible = this.applePayConfig.isEligible;

        if (isEligible) {
            this.fetchTransactionInfo().then(() => {
                const isSubscriptionProduct = this.ppcpConfig?.data_client_id?.has_subscriptions === true;
                if (isSubscriptionProduct) {
                    return;
                }
                this.addButton();
                const id_minicart = "#apple-" + this.buttonConfig.button.mini_cart_wrapper;
                const id = "#apple-" + this.buttonConfig.button.wrapper;

                if (this.context === 'mini-cart') {
                    document.querySelector(id_minicart)?.addEventListener('click', (evt) => {
                        evt.preventDefault();
                        this.onButtonClick();
                    });
                } else {
                    document.querySelector(id)?.addEventListener('click', (evt) => {
                        evt.preventDefault();
                        this.onButtonClick();
                    });
                }
            });
        }
    }

    reinit() {
        if (!this.applePayConfig) {
            return;
        }

        this.isInitialized = false;
        this.init(this.applePayConfig);
    }

    async fetchTransactionInfo() {
        this.transactionInfo = await this.contextHandler.transactionInfo();
    }

    /**
     * Returns configurations relative to this button context.
     */
    contextConfig() {
        let config = {
            wrapper: this.buttonConfig.button.wrapper,
            ppcpStyle: this.ppcpConfig.button.style,
            //buttonStyle: this.buttonConfig.button.style,
            ppcpButtonWrapper: this.ppcpConfig.button.wrapper
        }

        if (this.context === 'mini-cart') {
            config.wrapper = this.buttonConfig.button.mini_cart_wrapper;
            config.ppcpStyle = this.ppcpConfig.button.mini_cart_style;
            config.buttonStyle = this.buttonConfig.button.mini_cart_style;
            config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper;
        }

        if (['cart-block', 'checkout-block'].indexOf(this.context) !== -1) {
            config.ppcpButtonWrapper = '#express-payment-method-ppcp-gateway';
        }

        return config;
    }

    initEventHandlers() {
        const { wrapper, ppcpButtonWrapper } = this.contextConfig();
        const wrapper_id = '#' + wrapper;

        const syncButtonVisibility = () => {
            const $ppcpButtonWrapper = jQuery(ppcpButtonWrapper);
            setVisible(wrapper_id, $ppcpButtonWrapper.is(':visible'));
            setEnabled(wrapper_id, !$ppcpButtonWrapper.hasClass('ppcp-disabled'));
        }

        jQuery(document).on('ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', (ev, data) => {
            if (jQuery(data.selector).is(ppcpButtonWrapper)) {
                syncButtonVisibility();
            }
        });

        syncButtonVisibility();
    }

    /**
     * Starts an ApplePay session.
     */
    applePaySession(paymentRequest) {
        this.log('applePaySession', paymentRequest);
        const session = new ApplePaySession(4, paymentRequest);
        session.begin();

        if (this.shouldRequireShippingInButton()) {
            session.onshippingmethodselected = this.onShippingMethodSelected(session);
            session.onshippingcontactselected = this.onShippingContactSelected(session);
        }
        session.onvalidatemerchant = this.onValidateMerchant(session);
        session.onpaymentauthorized = this.onPaymentAuthorized(session);
        return session;
    }

    /**
     * Adds an Apple Pay purchase button.
     */
    addButton() {
        this.log('addButton', this.context);

        const wrapper =
            (this.context === 'mini-cart')
                ? this.buttonConfig.button.mini_cart_wrapper
                : this.buttonConfig.button.wrapper;
        const shape =
            (this.context === 'mini-cart')
                ? this.ppcpConfig.button.mini_cart_style.shape
                : this.ppcpConfig.button.style.shape;
        const appleContainer = document.getElementById(wrapper);
        const type = this.buttonConfig.button.type;
        const language = this.buttonConfig.button.lang;
        const color = this.buttonConfig.button.color;
        const id = "apple-" + wrapper;

        if (appleContainer) {
            appleContainer.innerHTML = `<apple-pay-button id="${id}" buttonstyle="${color}" type="${type}" locale="${language}">`;
        }

        jQuery('#' + wrapper).addClass('ppcp-button-' + shape);
        jQuery(wrapper).append(appleContainer);
    }

    //------------------------
    // Button click
    //------------------------

    /**
     * Show Apple Pay payment sheet when Apple Pay payment button is clicked
     */
    async onButtonClick() {
        this.log('onButtonClick', this.context);

        const paymentRequest = this.paymentRequest();

        // Trigger woocommerce validation if we are in the checkout page.
        if (this.context === 'checkout') {
            const checkoutFormSelector = 'form.woocommerce-checkout';
            const errorHandler = new ErrorHandler(
                PayPalCommerceGateway.labels.error.generic,
                document.querySelector('.woocommerce-notices-wrapper')
            );
            try {
                const formData = new FormData(document.querySelector(checkoutFormSelector));
                this.formData = Object.fromEntries(formData.entries());

                this.updateRequestDataWithForm(paymentRequest);
            } catch (error) {
                console.error(error);
            }

            this.log('=== paymentRequest', paymentRequest);

            const session = this.applePaySession(paymentRequest);
            const formValidator = PayPalCommerceGateway.early_checkout_validation_enabled ?
                new FormValidator(
                    PayPalCommerceGateway.ajax.validate_checkout.endpoint,
                    PayPalCommerceGateway.ajax.validate_checkout.nonce,
                ) : null;
            if (formValidator) {
                try {
                    const errors = await formValidator.validate(document.querySelector(checkoutFormSelector));
                    if (errors.length > 0) {
                        errorHandler.messages(errors);
                        jQuery( document.body ).trigger( 'checkout_error' , [ errorHandler.currentHtml() ] );
                        session.abort();
                        return;
                    }
                } catch (error) {
                    console.error(error);
                }
            }
            return;
        }

        // Default session initialization.
        this.applePaySession(paymentRequest);
    }

    /**
     * If the button should show the shipping fields.
     *
     * @returns {false|*}
     */
    shouldRequireShippingInButton() {
        return this.contextHandler.shippingAllowed()
            && this.buttonConfig.product.needShipping
            && (this.context !== 'checkout' || this.shouldUpdateButtonWithFormData());
    }

    /**
     * If the button should be updated with the form addresses.
     *
     * @returns {boolean}
     */
    shouldUpdateButtonWithFormData() {
        if (this.context !== 'checkout') {
            return false;
        }
        return this.buttonConfig?.preferences?.checkout_data_mode === 'use_applepay';
    }

    /**
     * Indicates how payment completion should be handled if with the context handler default actions.
     * Or with ApplePay module specific completion.
     *
     * @returns {boolean}
     */
    shouldCompletePaymentWithContextHandler() {
        // Data already handled, ex: PayNow
        if (!this.contextHandler.shippingAllowed()) {
            return true;
        }
        // Use WC form data mode in Checkout.
        if (this.context === 'checkout' && !this.shouldUpdateButtonWithFormData()) {
            return true;
        }
        return false;
    }

    /**
     * Updates ApplePay paymentRequest with form data.
     */
    updateRequestDataWithForm(paymentRequest) {
        if (!this.shouldUpdateButtonWithFormData()) {
            return;
        }

        // Add billing address.
        paymentRequest.billingContact = this.fillBillingContact(this.formData);

        // Add custom data.
        // "applicationData" is originating a "PayPalApplePayError: An internal server error has occurred" on paypal.Applepay().confirmOrder().
        // paymentRequest.applicationData = this.fillApplicationData(this.formData);

        if (!this.shouldRequireShippingInButton()) {
            return;
        }

        // Add shipping address.
        paymentRequest.shippingContact = this.fillShippingContact(this.formData);

        // Get shipping methods.
        const rate = this.transactionInfo.chosenShippingMethods[0];
        paymentRequest.shippingMethods = [];

        // Add selected shipping method.
        for (const shippingPackage of this.transactionInfo.shippingPackages) {
            if (rate === shippingPackage.id) {
                const shippingMethod = {
                    'label'      : shippingPackage.label,
                    'detail'     : '',
                    'amount'     : shippingPackage.cost_str,
                    'identifier' : shippingPackage.id,
                };

                // Remember this shipping method as the selected one.
                this.selectedShippingMethod = shippingMethod;

                paymentRequest.shippingMethods.push(shippingMethod);
                break;
            }
        }

        // Add other shipping methods.
        for (const shippingPackage of this.transactionInfo.shippingPackages) {
            if (rate !== shippingPackage.id) {
                paymentRequest.shippingMethods.push({
                    'label'      : shippingPackage.label,
                    'detail'     : '',
                    'amount'     : shippingPackage.cost_str,
                    'identifier' : shippingPackage.id,
                });
            }
        }

        // Store for reuse in case this data is not provided by ApplePay on authorization.
        this.initialPaymentRequest = paymentRequest;

        this.log('=== paymentRequest.shippingMethods', paymentRequest.shippingMethods);
    }

    paymentRequest() {
        const applepayConfig = this.applePayConfig
        const buttonConfig = this.buttonConfig
        let baseRequest = {
            countryCode: applepayConfig.countryCode,
            merchantCapabilities: applepayConfig.merchantCapabilities,
            supportedNetworks: applepayConfig.supportedNetworks,
            requiredShippingContactFields: ["postalAddress", "email", "phone"],
            requiredBillingContactFields: ["postalAddress"], // ApplePay does not implement billing email and phone fields.
        }

        if (!this.shouldRequireShippingInButton()) {
            if (this.shouldCompletePaymentWithContextHandler()) {
                // Data needs handled externally.
                baseRequest.requiredShippingContactFields = [];
            } else {
                // Minimum data required for order creation.
                baseRequest.requiredShippingContactFields = ["email", "phone"];
            }
        }

        const paymentRequest = Object.assign({}, baseRequest);
        paymentRequest.currencyCode = buttonConfig.shop.currencyCode;
        paymentRequest.total = {
            label: buttonConfig.shop.totalLabel,
            type: "final",
            amount: this.transactionInfo.totalPrice,
        }

        return paymentRequest;
    }

    refreshContextData() {
        switch (this.context) {
            case 'product':
                // Refresh product data that makes the price change.
                this.productQuantity = document.querySelector('input.qty')?.value;
                this.products = this.contextHandler.products();
                this.log('Products updated', this.products);
                break;
        }
    }

    //------------------------
    // Payment process
    //------------------------

    onValidateMerchant(session) {
        this.log('onvalidatemerchant', this.buttonConfig.ajax_url);
        return (applePayValidateMerchantEvent) => {
            this.log('onvalidatemerchant call');

            widgetBuilder.paypal.Applepay().validateMerchant({
                validationUrl: applePayValidateMerchantEvent.validationURL
            })
                .then(validateResult => {
                    this.log('onvalidatemerchant ok');
                    session.completeMerchantValidation(validateResult.merchantSession);
                    //call backend to update validation to true
                    jQuery.ajax({
                        url: this.buttonConfig.ajax_url,
                        type: 'POST',
                        data: {
                            action: 'ppcp_validate',
                            validation: true,
                            'woocommerce-process-checkout-nonce': this.nonce,
                        }
                    })
                })
                .catch(validateError => {
                    this.log('onvalidatemerchant error', validateError);
                    console.error(validateError);
                    //call backend to update validation to false
                    jQuery.ajax({
                        url: this.buttonConfig.ajax_url,
                        type: 'POST',
                        data: {
                            action: 'ppcp_validate',
                            validation: false,
                            'woocommerce-process-checkout-nonce': this.nonce,
                        }
                    });
                    this.log('onvalidatemerchant session abort');
                    session.abort();
                });
        };
    }

    onShippingMethodSelected(session) {
        this.log('onshippingmethodselected', this.buttonConfig.ajax_url);
        const ajax_url = this.buttonConfig.ajax_url;
        return (event) => {
            this.log('onshippingmethodselected call');

            const data = this.getShippingMethodData(event);

            jQuery.ajax({
                url: ajax_url,
                method: 'POST',
                data: data,
                success: (applePayShippingMethodUpdate, textStatus, jqXHR) => {
                    this.log('onshippingmethodselected ok');
                    let response = applePayShippingMethodUpdate.data;
                    if (applePayShippingMethodUpdate.success === false) {
                        response.errors = createAppleErrors(response.errors);
                    }
                    this.selectedShippingMethod = event.shippingMethod;

                    // Sort the response shipping methods, so that the selected shipping method is the first one.
                    response.newShippingMethods = response.newShippingMethods.sort((a, b) => {
                        if (a.label === this.selectedShippingMethod.label) {
                            return -1;
                        }
                        return 1;
                    });

                    if (applePayShippingMethodUpdate.success === false) {
                        response.errors = createAppleErrors(response.errors);
                    }
                    session.completeShippingMethodSelection(response);
                },
                error: (jqXHR, textStatus, errorThrown) => {
                    this.log('onshippingmethodselected error', textStatus);
                    console.warn(textStatus, errorThrown);
                    session.abort();
                },
            });
        };
    }

    onShippingContactSelected(session) {
        this.log('onshippingcontactselected', this.buttonConfig.ajax_url);

        const ajax_url = this.buttonConfig.ajax_url;

        return (event) => {
            this.log('onshippingcontactselected call');

            const data = this.getShippingContactData(event);

            jQuery.ajax({
                url: ajax_url,
                method: 'POST',
                data: data,
                success: (applePayShippingContactUpdate, textStatus, jqXHR) => {
                    this.log('onshippingcontactselected ok');
                    let response = applePayShippingContactUpdate.data;
                    this.updatedContactInfo = event.shippingContact;
                    if (applePayShippingContactUpdate.success === false) {
                        response.errors = createAppleErrors(response.errors);
                    }
                    if (response.newShippingMethods) {
                        this.selectedShippingMethod = response.newShippingMethods[0];
                    }
                    session.completeShippingContactSelection(response);
                },
                error: (jqXHR, textStatus, errorThrown) => {
                    this.log('onshippingcontactselected error', textStatus);
                    console.warn(textStatus, errorThrown);
                    session.abort();
                },
            });
        };
    }

    getShippingContactData(event) {
        const product_id = this.buttonConfig.product.id;

        this.refreshContextData();

        switch (this.context) {
            case 'product':
                return {
                    action: 'ppcp_update_shipping_contact',
                    product_id: product_id,
                    products: JSON.stringify(this.products),
                    caller_page: 'productDetail',
                    product_quantity: this.productQuantity,
                    simplified_contact: event.shippingContact,
                    need_shipping: this.shouldRequireShippingInButton(),
                    'woocommerce-process-checkout-nonce': this.nonce,
                };
            case 'cart':
            case 'checkout':
            case 'cart-block':
            case 'checkout-block':
            case 'mini-cart':
                return {
                    action: 'ppcp_update_shipping_contact',
                    simplified_contact: event.shippingContact,
                    caller_page: 'cart',
                    need_shipping: this.shouldRequireShippingInButton(),
                    'woocommerce-process-checkout-nonce': this.nonce,
                };
        }
    }

    getShippingMethodData(event) {
        const product_id = this.buttonConfig.product.id;

        this.refreshContextData();

        switch (this.context) {
            case 'product': return {
                action: 'ppcp_update_shipping_method',
                shipping_method: event.shippingMethod,
                simplified_contact: this.updatedContactInfo || this.initialPaymentRequest.shippingContact || this.initialPaymentRequest.billingContact,
                product_id: product_id,
                products: JSON.stringify(this.products),
                caller_page: 'productDetail',
                product_quantity: this.productQuantity,
                'woocommerce-process-checkout-nonce': this.nonce,
            }
            case 'cart':
            case 'checkout':
            case 'cart-block':
            case 'checkout-block':
            case 'mini-cart':
                return {
                    action: 'ppcp_update_shipping_method',
                    shipping_method: event.shippingMethod,
                    simplified_contact: this.updatedContactInfo || this.initialPaymentRequest.shippingContact || this.initialPaymentRequest.billingContact,
                    caller_page: 'cart',
                    'woocommerce-process-checkout-nonce': this.nonce,
                }
        }
    }

    onPaymentAuthorized(session) {
        this.log('onpaymentauthorized');
        return async (event) => {
            this.log('onpaymentauthorized call');

            function form() {
                return document.querySelector('form.cart');
            }
            const processInWooAndCapture = async (data) => {
                return new Promise((resolve, reject) => {
                    try {
                        const billingContact = data.billing_contact || this.initialPaymentRequest.billingContact;
                        const shippingContact = data.shipping_contact || this.initialPaymentRequest.shippingContact;
                        const shippingMethod = this.selectedShippingMethod || (this.initialPaymentRequest.shippingMethods || [])[0];

                        let request_data = {
                            action: 'ppcp_create_order',
                            'caller_page': this.context,
                            'product_id': this.buttonConfig.product.id ?? null,
                            'products': JSON.stringify(this.products),
                            'product_quantity': this.productQuantity ?? null,
                            'shipping_contact': shippingContact,
                            'billing_contact': billingContact,
                            'token': event.payment.token,
                            'shipping_method': shippingMethod,
                            'woocommerce-process-checkout-nonce': this.nonce,
                            'funding_source': 'applepay',
                            '_wp_http_referer': '/?wc-ajax=update_order_review',
                            'paypal_order_id': data.paypal_order_id,
                        };

                        this.log('onpaymentauthorized request', this.buttonConfig.ajax_url, data);

                        jQuery.ajax({
                            url: this.buttonConfig.ajax_url,
                            method: 'POST',
                            data: request_data,
                            complete: (jqXHR, textStatus) => {
                                this.log('onpaymentauthorized complete');
                            },
                            success: (authorizationResult, textStatus, jqXHR) => {
                                this.log('onpaymentauthorized ok');
                                resolve(authorizationResult);
                            },
                            error: (jqXHR, textStatus, errorThrown) => {
                                this.log('onpaymentauthorized error', textStatus);
                                reject(new Error(errorThrown));
                            },
                        });
                    } catch (error) {
                        this.log('onpaymentauthorized catch', error);
                        console.log(error);  // handle error
                    }
                });
            }

            let id = await this.contextHandler.createOrder();

            this.log('onpaymentauthorized paypal order ID', id, event.payment.token, event.payment.billingContact);

            try {
                const confirmOrderResponse = await widgetBuilder.paypal.Applepay().confirmOrder({
                    orderId: id,
                    token: event.payment.token,
                    billingContact: event.payment.billingContact,
                });

                this.log('onpaymentauthorized confirmOrderResponse', confirmOrderResponse);

                if (confirmOrderResponse && confirmOrderResponse.approveApplePayPayment) {
                    if (confirmOrderResponse.approveApplePayPayment.status === "APPROVED") {
                        try {

                            if (this.shouldCompletePaymentWithContextHandler()) {
                                // No shipping, expect immediate capture, ex: PayNow, Checkout with form data.

                                let approveFailed = false;
                                await this.contextHandler.approveOrder({
                                    orderID: id
                                }, { // actions mock object.
                                    restart: () => new Promise((resolve, reject) => {
                                        approveFailed = true;
                                        resolve();
                                    }),
                                    order: {
                                        get: () => new Promise((resolve, reject) => {
                                            resolve(null);
                                        })
                                    }
                                });

                                if (!approveFailed) {
                                    this.log('onpaymentauthorized approveOrder OK');
                                    session.completePayment(ApplePaySession.STATUS_SUCCESS);
                                } else {
                                    this.log('onpaymentauthorized approveOrder FAIL');
                                    session.completePayment(ApplePaySession.STATUS_FAILURE);
                                    session.abort();
                                    console.error(error);
                                }

                            } else {
                                // Default payment.

                                let data = {
                                    billing_contact: event.payment.billingContact,
                                    shipping_contact: event.payment.shippingContact,
                                    paypal_order_id: id,
                                };
                                let authorizationResult = await processInWooAndCapture(data);
                                if (authorizationResult.result === "success") {
                                    session.completePayment(ApplePaySession.STATUS_SUCCESS);
                                    window.location.href = authorizationResult.redirect;
                                } else {
                                    session.completePayment(ApplePaySession.STATUS_FAILURE);
                                }

                            }

                        } catch (error) {
                            session.completePayment(ApplePaySession.STATUS_FAILURE);
                            session.abort();
                            console.error(error);
                        }
                    } else {
                        console.error('Error status is not APPROVED');
                        session.completePayment(ApplePaySession.STATUS_FAILURE);
                    }
                } else {
                    console.error('Invalid confirmOrderResponse');
                    session.completePayment(ApplePaySession.STATUS_FAILURE);
                }
            } catch (error) {
                console.error('Error confirming order with applepay token', error);
                session.completePayment(ApplePaySession.STATUS_FAILURE);
                session.abort();
            }
        };
    }

    fillBillingContact(data) {
        return {
            givenName:          data.billing_first_name ?? '',
            familyName:         data.billing_last_name ?? '',
            emailAddress:       data.billing_email  ?? '',
            phoneNumber:        data.billing_phone ?? '',
            addressLines:       [data.billing_address_1, data.billing_address_2],
            locality:           data.billing_city ?? '',
            postalCode:         data.billing_postcode ?? '',
            countryCode:        data.billing_country ?? '',
            administrativeArea: data.billing_state ?? '',
        }
    }

    fillShippingContact(data) {
        if (data.shipping_first_name === "") {
            return this.fillBillingContact(data);
        }
        return {
            givenName:          (data?.shipping_first_name && data.shipping_first_name !== "") ? data.shipping_first_name : data?.billing_first_name,
            familyName:         (data?.shipping_last_name && data.shipping_last_name !== "") ? data.shipping_last_name : data?.billing_last_name,
            emailAddress:       (data?.shipping_email && data.shipping_email !== "") ? data.shipping_email : data?.billing_email,
            phoneNumber:        (data?.shipping_phone && data.shipping_phone !== "") ? data.shipping_phone : data?.billing_phone,
            addressLines:       [data.shipping_address_1 ?? '', data.shipping_address_2 ?? ''],
            locality:           (data?.shipping_city && data.shipping_city !== "") ? data.shipping_city : data?.billing_city,
            postalCode:         (data?.shipping_postcode && data.shipping_postcode !== "") ? data.shipping_postcode : data?.billing_postcode,
            countryCode:        (data?.shipping_country && data.shipping_country !== "") ? data.shipping_country : data?.billing_country,
            administrativeArea: (data?.shipping_state && data.shipping_state !== "") ? data.shipping_state : data?.billing_state,
        }
    }

    fillApplicationData(data) {
        const jsonString = JSON.stringify(data);
        let utf8Str = encodeURIComponent(jsonString).replace(/%([0-9A-F]{2})/g, (match, p1) => {
            return String.fromCharCode('0x' + p1);
        });

        return btoa(utf8Str);
    }
}

export default ApplepayButton;
