'use strict';

/**
 * Checkout Manager.
 *
 * Helps execute the checkout process
 *
 * @version 0.1.25
 */
;(function () {
    window.NorthCommerce = window.NorthCommerce ?? {}

    const api = window.NorthCommerce.DataApi

    let placeholder = function (what, data) {
        return new Promise((resolve, reject) => {
            console.log(what, data)
            resolve(data)
        })
    }

    window.NorthCommerce.CheckoutManager = {
        setEntity(name, entity) {
            let entities = this.allEntities()
            entities[name] = entity
            localStorage.setItem('nc-checkout-entities', JSON.stringify(entities))
        },

        getEntity(name) {
            let entities = this.allEntities()
            return entities[name]
        },

        allEntities() {
            const entities = localStorage.getItem('nc-checkout-entities')
            return entities ? JSON.parse(entities) : {}
        },

        resetEntities() {
            localStorage.removeItem('nc-checkout-entities')
            NorthCommerce.CouponManager.resetStoredData()
        },

        /**
         * We don't know which payment provider the user will pick. But,
         * we know stripe takes a while to set up. So, set it up in the background
         * and throw it away if we don't need it.
         */
        prewarmPaymentProvider() {
            if (window.NorthCommerce.StripeHelper.isEnabled()) {
                setTimeout(() => this.setPaymentProvider('stripe'), 100);
            }
        },

        async setCustomer(customerInfo) {
            let currentCustomer = this.getEntity('customer')

            const customer = await api.post('customers', {
                email: customerInfo.email,
                marketing_optin: customerInfo.marketing_optin,
            })

            if (!currentCustomer || currentCustomer.id != customer.id) {
                this.resetEntities()
            }

            this.setEntity('customer', customer)
        },

        setAddress(type, address) {
            let updateOrder = (address) =>
                this.theOrder().then((order) => {
                    var payload = {}
                    payload[type + '_address_id'] = address ? address.id : null
                    return api.post(`orders/${order.id}`, payload).then((order) => {
                        this.setEntity('order', order)
                        return address
                    })
                })

            if (address) {
                if (!this.getEntity('customer')) {
                    throw new Error("Customer isn't set, can't save an address")
                }

                return api
                    .get('countries', {
                        filter: `abbreviation:eq:${address.country}`,
                    })
                    .then((found) => {
                        if (found.length == 0) {
                            throw new Error(`No country found for: ${address.country}`)
                        }

                        delete address.country
                        address.country_id = found[0].id

                        let a = this.getEntity(type + 'Address')

                        if (!a) {
                            address.customer_id = this.getEntity('customer').id
                        }

                        let path = a ? `addresses/${a.id}` : 'addresses'

                        return api
                            .post(path, address)
                            .then((address) => {
                                this.setEntity(`${type}Address`, address)
                                return address
                            })
                            .then(updateOrder)
                    })
            } else {
                return new Promise((resolve, reject) => {
                    this.setEntity(`${type}Address`, null)
                }).then(updateOrder)
            }
        },

        async setShippingDetail(payload) {
            const shippingAddress = this.getEntity('shippingAddress')

            if (!shippingAddress) {
                return
            }

            const existing = this.getEntity('shippingDetail')
            let updated = null
            let order = await this.theOrder()
            payload.order_id = order.id

            if (existing) {
                updated = await api.post(`shipping-details/${existing.id}`, payload)
            } else {
                let sdTypes = await api.get('shipping-detail-types', {
                    filter: 'slug:eq:planned',
                })
                payload.shipping_detail_type_id = sdTypes[0].id
                updated = await api.post(`shipping-details`, payload)
            }
            this.setEntity('shippingDetail', updated)
            this.setEntity('order', await api.get(`orders/${order.id}`))
            await this.recalcOrder()
        },

        clearShippingDetail: async function () {
            let order = await this.theOrder()
            const existing = this.getEntity('shippingDetail')
            // XXX need to 'delete' out the shipping detail
        },

        async setPaymentProvider(provider) {
            console.log('setPaymentProvider', provider)

            const customer = this.getEntity('customer')

            if (!customer) {
                throw new Error('Cutomer not set, giving up')
            }

            const cpm = await this.setupCustomerPaymentMethod(provider)
            const ot = await this.theOrderTransaction()
            const updatedOt = await api.post(`order-transactions/${ot.id}`, {
                customer_payment_method_id: cpm.id,
            })

            const finalize = {
                stripe() {
                    const stripeHelper = NorthCommerce.StripeHelper
                    if (updatedOt.stripe_client_secret) {
                        stripeHelper.clientSecret = updatedOt.stripe_client_secret
                        this.setEntity('stripeClientSecret', stripeHelper.clientSecret)
                        delete ot.stripe_client_secret
                        return
                    }

                    if (this.getEntity('stripeClientSecret')) {
                        stripeHelper.clientSecret = this.getEntity('stripeClientSecret')
                        return
                    }

                    throw new Error("Can't derive stripe client secret")
                },
            }

            if (finalize[provider]) {
                finalize[provider].call(this)
            }

            this.setEntity('orderTransaction', updatedOt)
            return updatedOt
        },

        async setPaymentMethod (payment) {
            const customer = this.getEntity('customer')

            if (!customer) {
                throw new Error('Cutomer not set, giving up')
            }

            const payload = {
                brand: null,
                last4: null,
                expiration_year: null,
                expiration_month: null,
                token: null,
            }

            const maybeSet = ['token', 'brand', 'last4', 'expiration_month', 'expiration_year']
            maybeSet.forEach((attr) => {
                if (payment.data[attr]) {
                    payload[attr] = payment.data[attr]
                }
            })


            console.log('setPaymentMethod', payload)

            const paymentProvider = this.getEntity('paymentProvider')
            const cpm = this.getEntity('customerPaymentMethod')
            const updatedCpm = await api.post(`customer-payment-methods/${cpm.id}`, payload)
            this.setEntity('customerPaymentMethod', updatedCpm)
            this.setEntity(`${paymentProvider.type}_customerPaymentMethod`, updatedCpm)

            const o = await this.theOrder()
            const updatedO = await api.post(`orders/${o.id}`, {
                customer_payment_method_id: updatedCpm.id,
            })
            this.setEntity('order', updatedO)

            const ot = this.getEntity('orderTransaction')
            const updatedOt = await api.post(`order-transactions/${ot.id}`, {
                customer_payment_method_id: updatedCpm.id,
            })
            this.setEntity('orderTransaction', updatedOt)

            return updatedCpm
        },

        async setLineItems(lineItems) {
            const order = await this.theOrder()
            let tasks = lineItems.map(async (li) => {
                let pv = await this.lookupProductVariant(li.product_variant_id)
                let existing = this.getLineItemEntity(pv.id)
                let payload = {
                    product_variant_id: pv.id,
                    quantity: li.qty,
                }

                if (!existing) {
                    payload.order_id = order.id
                }

                const path = existing ? `line-items/${existing.id}` : 'line-items'
                return api.post(path, payload)
            })

            return Promise.all(tasks).then((updatedLineItems) => {
                this.setEntity('lineItems', updatedLineItems)
                this.recalcOrder()
                return updatedLineItems
            })
        },

        async setCouponOrderMap(coupon) {
            if (!coupon) {
                const couponData = NorthCommerce.CouponManager.getLocalCouponData()

                if (!couponData.length) {
                    return
                }

                coupon = couponData[0]
            }

            const o = await this.theOrder()

            if (!o) {
                throw new Error("Order doesn't exist yet.")
            }

            const comid = this.getEntity('couponOrderMapID')
            const couponOrderMapID = await NorthCommerce.CouponManager.mapCouponWithOrder({
                order_id: o.id,
                promocode: coupon.promocode,
            })

            this.setEntity('couponOrderMapID', couponOrderMapID)
        },

        async updateLineItem(lineItem) {
            if (!this.getEntity('order')) {
                return
            }

            const li = this.getLineItemEntity(lineItem.product_variant_id)

            if (!li) {
                return // revise
                throw new Error(`Line item: ${lineItem.product_variant_id} doesn't exist`)
            }

            // let pv = await this.lookupProductVariant(li.product_variant_id);
            const updatedLi = await api.post(`line-items/${li.id}`, {
                quantity: lineItem.qty,
            })
            this.setLineItemEntity(updatedLi)
            await this.recalcOrder()
        },

       async removeLineItem(lineItem) {
           const entities = this.getEntity('lineItems');
           if(!entities) {
               console.warn('No line items found in localStorage')
               return;
            }

            const matchingLineItem = entities.find(item => item.product_variant_id === lineItem.product_variant_id);
            if (!matchingLineItem) {
                console.warn('No matching line item found.');
                return;
            }

           await api.nuke(`line-items/${matchingLineItem.id}`, {});
           const updatedEntities = entities.filter(item => item.id !== matchingLineItem.id);
           this.setEntity('lineItems', updatedEntities);
           await this.recalcOrder()
        },

        async theOrder() {
            if (this.getEntity('order')) {
                return new Promise((resolve, reject) => {
                    resolve(this.getEntity('order'))
                })
            }

            if (!this.getEntity('customer')) {
                throw new Error("Customer isn't define yet")
            }

            const order = await api.post('orders', {
                customer_id: this.getEntity('customer').id,
            })

            this.setEntity('order', order)
            this.setCouponOrderMap()

            return order
        },

        async theOrderTransaction() {
            const existingOt = this.getEntity('orderTransaction')

            if (existingOt) {
                return existingOt
            }

            const order = await this.theOrder()
            const ot = await api.post('order-transactions', {
                order_id: order.id,
            })

            this.setEntity('orderTransaction', ot)
            return ot
        },

        setupCustomerPaymentMethod: async function (provider) {
            const paymentProvider = await this.lookupBySlug('payment-providers', provider)
            this.setEntity('paymentProvider', paymentProvider)

            let cpm = this.getEntity(`${provider}_customerPaymentMethod`)

            if (!cpm) {
                const customer = this.getEntity('customer')
                cpm = await api.post('customer-payment-methods', {
                    payment_provider_id: paymentProvider.id,
                    customer_id: customer.id,
                })
            }

            this.setEntity('customerPaymentMethod', cpm)
            this.setEntity(`${provider}_customerPaymentMethod`, cpm)
            return cpm
        },

        recalcOrder: async function () {
            const o = await this.theOrder()
            const ot = await this.theOrderTransaction()

            this.setEntity('order', await api.get(`orders/${o.id}`))
            this.setEntity('orderTransaction', await api.get(`order-transactions/${ot.id}`))
            this.setCouponOrderMap()
        },

        getLineItemEntity(productVariantId) {
            let all = this.getEntity('lineItems')

            if (!all) {
                return null
            }

            let found = all.filter((li) => li.product_variant_id == productVariantId)
            return found.length > 0 ? found[0] : null
        },

        setLineItemEntity(lineItem) {
            let all = this.getEntity('lineItems')

            if (!all) {
                this.setEntity('lineItems', [li])
                return
            }

            let found = all.filter((li) => li.id == lineItem.id)
            if (!found.length) {
                all.push(lineItem)
                this.setEntity('lineItems', all)
                return
            }

            let updated = all.map((li) => {
                return li.id == lineItem.id ? lineItem : li
            })
            this.setEntity('lineItems', updated)

        },

        async placeOrder() {
            const paymentProvider = this.getEntity('paymentProvider')
            const order = await this.theOrder()
            switch (paymentProvider.slug) {
                case 'stripe':
                    this.placeOrderStarted()
                    const stripeHelper = NorthCommerce.StripeHelper
                    const result = await stripeHelper.stripeApi.confirmPayment({
                        elements: stripeHelper.elements,
                        confirmParams: {
                            return_url: northCommerceSettings.stripeConfirmPaymentURL,
                        },
                    })
                    await this.placeOrderFinished(order, result)
                    break
                case 'square':
                    console.log('order', order)
                    this.placeOrderStarted();
                    const result_square = await NorthCommerce.SquareHelper.pay(order);
                    console.log(result_square)
                    this.placeOrderFinished(order, result_square);
                    break;

                default:
                    throw new Error(`${paymentProvider.slug} not yet implemented`)
            }
        },

        placeOrderStarted() {
            const button = document.querySelector('.nc-place-order-btn')
            if (button) {
                button.disabled = true
                button.innerHTML = 'Submitting...'
            }
            this.archiveLocalStorage()
        },

        async placeOrderFinished(order, outcome) {
            console.log('placeOrderFinished', order, outcome)

            if (outcome.error) {
                const button = document.querySelector('.nc-place-order-btn')
                if (button) {
                    button.disabled = false
                    button.innerHTML = 'Place your order'
                }

                alert(outcome.error.message)
                this.reviveLocalStorage()
                return
            }

            NorthCommerce.CouponManager.resetStoredData()
            window.location = ajax_data.checkout_thank_you_url + '?order=' + order['id']
        },

        lookupBySlug(entity, slug) {
            return api
                .get(entity, {
                    filter: `slug:eq:${slug}`,
                })
                .then((found) => {
                    if (!found.length) {
                        throw new Error(`${entity}.${slug} not found`)
                    }
                    return found[0]
                })
        },

        lookupProductVariant(productVariantId) {
            return api.get(`product-variants/${productVariantId}`, {
                expand: 'product',
            })
        },

        archiveLocalStorage() {
            const toSave = ['nc-cart', 'nc-checkout-entities']
            toSave.forEach((key) => {
                localStorage.setItem(`archived-${key}`, localStorage.getItem(key))
                localStorage.removeItem(key)
            })
        },

        reviveLocalStorage() {
            const toRestore = ['nc-cart', 'nc-checkout-entities']
            toRestore.forEach((key) => {
                const slug = `archived-${key}`
                localStorage.setItem(key, localStorage.getItem(slug))
                localStorage.removeItem(slug)
            })
        },
    }
})()
