/* eslint-disable camelcase */
import React, {createRef} from 'react'
import PropTypes from 'prop-types'
import PaymentClient from 'common-fe/clients/payment'
import inputNames from 'common-fe/constants/input-names'
import investmentApproaches from 'common-fe/constants/investment-approaches'
import {renderLargeNumber} from 'common-fe/utils/render'
import FormError, {reason} from 'common-fe/utils/form-error'
import {validateInput} from 'common-fe/utils/validation'
import {sparsifyObj} from 'common-fe/utils'
import {Provider} from './context'
import MultiStepForm from '../templates/multi-step'
import {
    PAYMENT_ADDRESS,
    PAYMENT_CHECKOUT,
    PAYMENT_LEDGER,
    PORTFOLIO_NAME,
    SPONSORSHIP_CODE,
} from './steps'
import {PERSONAL_PORTFOLIO_FUNDING_AMOUNTS} from 'store/config'

class PortfolioOnboardingForm extends React.PureComponent {
    // =========================================================================
    //  STATIC METHODS & PROPERTIES
    // =========================================================================

    static formName = 'portfolio-onboarding'

    static fundingOptions = PERSONAL_PORTFOLIO_FUNDING_AMOUNTS.map(
        (amount) => ({
            label: renderLargeNumber(amount),
            value: amount,
        })
    )

    static defaultState = {
        data: {
            [inputNames.ADDRESS_BUILDING]: {label: '', value: ''},
            [inputNames.ADDRESS_CITY]: '',
            [inputNames.ADDRESS_COUNTRY]: '',
            [inputNames.ADDRESS_POSTAL_CODE]: '',
            [inputNames.ADDRESS_REGION]: '',
            [inputNames.NAME]: '',
            [inputNames.STUDENT_ID]: '',
            [inputNames.COUPON_ID]: '',
            [inputNames.COURSE_ID]: '',
            [inputNames.FUNDING_AMOUNT]:
                PortfolioOnboardingForm.fundingOptions[0],
            [inputNames.INVESTMENT_APPROACH]: investmentApproaches.NONE.value,
            [inputNames.DESCRIPTION]: '',
            [inputNames.TOKEN]: '',
            [inputNames.ORDER_ID]: '',
        },
        error: {},
    }

    componentWillUnmount() {
        const script = document.getElementById('googleMapsScript')

        if (script) {
            document.body.removeChild(script)
        }
    }

    // =========================================================================
    //  INSTANCE METHODS & PROPERTIES
    // =========================================================================

    constructor(props) {
        super(props)

        // The steps in the portfolio onboarding flow is dependent on whether
        // we're onboarding for a course and if so, whether it is free or paid.
        const {course, user, onChange} = props
        const isCoursePortfolio = !!course

        if (isCoursePortfolio) {
            // Dynamically add Google Maps SDK script
            this.getGoogleMapsScript()

            // Get price for course
            PaymentClient.getSku({skuId: `sku_${course.id}`})
                .then(({price}) => {
                    const paymentSteps =
                        price > 0 ? [PAYMENT_ADDRESS, PAYMENT_CHECKOUT] : []

                    this.setState(
                        ({
                            childContext: prevChildContext,
                            steps: prevSteps,
                        }) => ({
                            childContext: {
                                ...prevChildContext,
                                order: {amount: price},
                            },
                            steps: [
                                ...prevSteps,
                                PAYMENT_LEDGER,
                                ...paymentSteps,
                            ],
                        })
                    )
                    return price
                })
                .catch((err) => {
                    // TODO: handle error fetching course price info.
                    console.error(err)
                })
                .then((price) => {
                    if (price > 0) {
                        // Get Stripe Customer details in case they have already entered address before
                        PaymentClient.getCustomer({userId: this.props.user.id})
                            .then((customer) => {
                                this.setState({customer}, () => {
                                    if (customer.address) {
                                        const {
                                            line1,
                                            city,
                                            country,
                                            postal_code,
                                            state,
                                        } = customer.address

                                        this.changeAddress({
                                            [inputNames.ADDRESS_BUILDING]: {
                                                label: line1,
                                                value: line1,
                                            },
                                            [inputNames.ADDRESS_CITY]: city,
                                            [inputNames.ADDRESS_COUNTRY]: country,
                                            [inputNames.ADDRESS_POSTAL_CODE]: postal_code,
                                            [inputNames.ADDRESS_REGION]: state,
                                        })
                                    }
                                })
                            })
                            .catch((err) => {
                                console.error(err)
                            })
                    } else {
                        const orderOptions = {
                            userId: user.id,
                            sku: `sku_${course.id}`,
                            metadata: {description: course.courseName},
                        }

                        PaymentClient.createOrder(orderOptions).then(({id}) => {
                            onChange?.({[inputNames.ORDER_ID]: id})
                        })
                        // .then(resolve)
                    }
                })
        }

        // We store the portfolioId in the event that we need to patch the
        // portfolio before completing the onboarding form.
        this.portfolioId = null

        this.state = {
            childContext: {
                course,
                onAddressChange: this.changeAddress,
                order: {amount: 0},
                user,
            },
            customer: null,
            steps: [PORTFOLIO_NAME],
        }

        this.paymentFormRef = createRef()
    }

    /**
     * Initializes the data for the form. Necessary for connecting the form
     * using our connected form HOC.
     */
    get initialState() {
        const {
            data: defaultData,
            error: defaultError,
        } = PortfolioOnboardingForm.defaultState

        const {
            course: {id: courseId = defaultData[inputNames.COURSE_ID]} = {},
            user: {studentNumber = defaultData[inputNames.STUDENT_ID]},
        } = this.props

        return {
            data: {
                ...defaultData,
                [inputNames.COURSE_ID]: courseId,
                [inputNames.STUDENT_ID]: studentNumber,
            },
            error: {...defaultError},
        }
    }

    getGoogleMapsScript() {
        const script = document.createElement('script')

        script.src =
            'https://maps.googleapis.com/maps/api/js?key=AIzaSyCHFsdwNN_7KqYwVl6MHHXXuq8rwqW7SFg&libraries=places'
        script.async = true
        script.id = 'googleMapsScript'

        document.body.appendChild(script)
    }

    change = ({name, value}) => {
        // If we're the coupon id input, we need to try to fetch the coupon
        // and provide feedback on whether it was successful or not.
        if (name === inputNames.COUPON_ID && this.props.data[name] !== value) {
            // TODO: handle coupon fetching
        }

        this.props.onChange && this.props.onChange({[name]: value})
    }

    changeAddress = (data) => {
        this.props.onChange?.(data)
    }

    update = ({name, value}) => {
        switch (name) {
            case inputNames.NAME:
            case inputNames.COUPON_ID: {
                const trimmedValue = value.trim()
                trimmedValue !== value &&
                    this.change({name, value: trimmedValue})
                return
            }

            default:
                return
        }
    }

    /**
     * Event hook that will trigger right before moving onto the next step
     * and can be used to determine if the user can proceed.
     * @returns {Promise}
     */
    willNext = (currIndex) =>
        new Promise((resolve, reject) => {
            const {steps} = this.state
            const step = steps[currIndex]
            const isLastStep = currIndex === steps.length - 1
            const {data, error, course, user, onChange, onError} = this.props

            // In the case where there aren't any inputs and it isn't the last step
            // to trigger, we can just proceed to the next step.
            if (
                (!Array.isArray(step.inputs) || !step.inputs.length) &&
                !isLastStep
            ) {
                return resolve()
            }

            // Check if there are any errors on the current step, if so reject.
            const hasError = step.inputs?.some(({name}) => error[name])

            if (hasError) {
                return reject(error)
            }

            // Custom handling for each step index

            // On this step, we create the portfolio
            if (step === PORTFOLIO_NAME && isLastStep) {
                return this.submitPortfolioData(data)
                    .then(resolve)
                    .catch(reject)
            }

            if (step === SPONSORSHIP_CODE) {
                return this.props
                    .getCoupon?.({couponId: data[inputNames.COUPON_ID]})
                    .then((coupon) => {
                        // A coupon can only be used if it has not expired or exceeded
                        // its redemption limit, and is intended for the course / user
                        if (
                            !coupon.valid ||
                            coupon.metadata.parentId !== course.id ||
                            coupon.metadata.firebaseId !== user.id
                        ) {
                            throw reason.INVALID_COUPON
                        }

                        // Attempt to make a payment
                        return this.props.makePayment?.({
                            couponId: coupon.id,
                            userId: user.id,
                        })
                    })
                    .catch((err) => {
                        if (!err.inlineErrors || !err.inlineErrors.length) {
                            onError?.({
                                ...error,
                                [inputNames.COUPON_ID]: reason.INVALID_COUPON,
                            })
                            return reject(reason.INVALID_COUPON)
                        }

                        const formError = new FormError(
                            Object.keys(
                                PortfolioOnboardingForm.defaultState.data
                            ),
                            err.inlineErrors
                        )

                        onError?.(formError)
                        return reject(error)
                    })
            }

            if (step === PAYMENT_ADDRESS) {
                const address = {
                    line1: data[inputNames.ADDRESS_BUILDING].value,
                    city: data[inputNames.ADDRESS_CITY],
                    country: data[inputNames.ADDRESS_COUNTRY],
                    postal_code: data[inputNames.ADDRESS_POSTAL_CODE],
                    state: data[inputNames.ADDRESS_REGION],
                }
                // TODO: check to see if address changed before updating customer
                PaymentClient.updateCustomer({address, userId: user.id})
                    .then(() => {
                        const orderOptions = {
                            userId: user.id,
                            sku: `sku_${course.id}`,
                            metadata: {description: course.courseName},
                        }

                        PaymentClient.createOrder(orderOptions)
                            .then(({id}) => {
                                onChange?.({[inputNames.ORDER_ID]: id})
                            })
                            .then(resolve)
                    })
                    // TODO: show error message
                    .catch(reject)
            }

            if (step === PAYMENT_LEDGER && isLastStep) {
                return this.submitPortfolioData(data)
                    .then(resolve)
                    .catch(reject)
            }

            if (step === PAYMENT_CHECKOUT) {
                const {current: paymentForm} = this.paymentFormRef

                // Get card payment token from Stripe and then create portfolio
                return paymentForm
                    ?.getPaymentToken()
                    .then(({token: {id: token}}) => {
                        const formData = {
                            ...data,
                            [inputNames.TOKEN]: token,
                        }

                        return this.submitPortfolioData(formData)
                            .then(resolve)
                            .catch(reject)
                    })
                    .catch(reject)
            }

            return resolve()
        })

    /**
     * Submitting portfolio data will either create a portfolio if one isn't
     * already created or update the portfolio that was created.
     */
    submitPortfolioData = ({
        [inputNames.NAME]: name,
        [inputNames.STUDENT_ID]: studentId,
        [inputNames.COURSE_ID]: courseId,
        [inputNames.FUNDING_AMOUNT]: {value: fundingAmount},
        [inputNames.INVESTMENT_APPROACH]: investmentApproach,
        [inputNames.DESCRIPTION]: description,
        [inputNames.ORDER_ID]: orderId,
        [inputNames.TOKEN]: token,
    }) => {
        const payload = sparsifyObj({
            [inputNames.PORTFOLIO_ID]: this.portfolioId,
            [inputNames.NAME]: name,
            [inputNames.STUDENT_ID]: studentId,
            [inputNames.COURSE_ID]: courseId,
            [inputNames.FUNDING_AMOUNT]: fundingAmount,
            [inputNames.INVESTMENT_APPROACH]: investmentApproach,
            [inputNames.DESCRIPTION]: description,
            [inputNames.ORDER_ID]: orderId,
            [inputNames.TOKEN]: token,
        })

        // Create the portfolio if it doesn't exist, otherwise patch it.
        const {
            createPortfolio,
            getSubscriptionStatus,
            onError,
            updatePortfolio,
            user,
        } = this.props
        const promise = this.portfolioId ? updatePortfolio : createPortfolio

        if (!payload[inputNames.TOKEN]) {
            delete payload[inputNames.TOKEN]
        }

        return promise(payload, {redirect: false})
            .then((portfolio) => {
                this.portfolioId = this.portfolioId || portfolio.id
                getSubscriptionStatus({userId: user.id})
            })
            .catch((err) => {
                let error = null

                if (err.inlineErrors && err.inlineErrors.length && onError) {
                    error = new FormError(
                        Object.keys(PortfolioOnboardingForm.defaultState.data),
                        err.inlineErrors
                    )
                } else {
                    // Handle errors that come directly from Stripe
                    error = {...err, message: err.errors?.[0]?.reason}
                }

                onError && onError(error)
                throw error
            })
    }

    render() {
        const {className, data, error, onError, onSubmit} = this.props

        return (
            <Provider value={this.state.childContext}>
                <MultiStepForm
                    className={className}
                    data={data}
                    error={error}
                    formName={PortfolioOnboardingForm.formName}
                    ref={this.paymentFormRef}
                    steps={this.state.steps}
                    validate={validateInput}
                    willNext={this.willNext}
                    onComplete={onSubmit}
                    onChange={this.change}
                    onError={onError}
                    onUpdate={this.update}
                />
            </Provider>
        )
    }
}

PortfolioOnboardingForm.propTypes = {
    className: PropTypes.string,
    course: PropTypes.shape({
        id: PropTypes.string,
        courseName: PropTypes.string,
        currentSeats: PropTypes.number,
        paidSeats: PropTypes.number,
    }),
    createPortfolio: PropTypes.func,
    data: PropTypes.shape({
        [inputNames.NAME]: PropTypes.string,
        [inputNames.FUNDING_AMOUNT]: PropTypes.object,
        [inputNames.STRATEGY]: PropTypes.object,
        [inputNames.DESCRIPTION]: PropTypes.string,
    }),
    error: PropTypes.shape({
        [inputNames.NAME]: PropTypes.string,
        [inputNames.FUNDING_AMOUNT]: PropTypes.string,
        [inputNames.STRATEGY]: PropTypes.string,
        [inputNames.DESCRIPTION]: PropTypes.string,
    }),
    getCoupon: PropTypes.func,
    getCourse: PropTypes.func,
    getSubscriptionStatus: PropTypes.func,
    makePayment: PropTypes.func,
    updatePortfolio: PropTypes.func,
    user: PropTypes.object,
    onChange: PropTypes.func,
    onError: PropTypes.func,
    onSubmit: PropTypes.func,
}

PortfolioOnboardingForm.defaultProps = {
    data: {...PortfolioOnboardingForm.defaultState.data},
    error: {...PortfolioOnboardingForm.defaultState.error},
}

export default PortfolioOnboardingForm
