import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { partTypeStrings } from "@/constants/part-type-strings";
import { deepClone } from "@/helpers/object-helper";
import baseMixin from "@/mixins/base-mixin.js";

export const promoPartNumberStrings = {
    WIPER_DISCOUNT_PART_NUMBER: "WIPER DISCOUNT",
    RAIN_DEFENSE_DISCOUNT_PART_NUMBER: "DISC RAIN DEFEN",
    GLASS_DISCOUNT_PART_NUMBER: "DISCOUNT",
    GLASS_CLEANER_DISCOUNT_PART_NUMBER: "DISC GLASS CLN",
};

export const addableVapsPromoPartNumbers = [
    promoPartNumberStrings.RAIN_DEFENSE_DISCOUNT_PART_NUMBER,
    promoPartNumberStrings.WIPER_DISCOUNT_PART_NUMBER,
];

export const promoErrorCodes = {
    UNKNOWN: "Unknown",
    PROMO_NOT_YET_IN_USE: "PromoNotYetInUse",
    PROMO_USAGE_COUNT_EXCEEDED: "PromoUsageCountExceeded",
    PROMO_EXPIRED: "PromoExpired",
    PROMO_DOES_NOT_EXIST: "PromoDoesNotExist",
    PROMO_STACKING_NOT_ALLOWED: "PromoStackingNotAllowed",
    SIMILAR_PROMO_ALREADY_ON_ORDER: "SimilarPromoAlreadyOnOrder",
    INVALID_PROMO_ON_ORDER: "InvalidPromoOnOrder",
    PROMO_VALIDATION_ERROR: "PromoValidationError",
    NO_SUITABLE_ITEM_FOR_PROMO: "NoSuitableItemForPromo",
};

const stackingPromoErrorCodes = [
    promoErrorCodes.PROMO_STACKING_NOT_ALLOWED,
    promoErrorCodes.SIMILAR_PROMO_ALREADY_ON_ORDER,
];

export const excludeFromInactivePromoErrorCodes = [
    promoErrorCodes.UNKNOWN,
    promoErrorCodes.PROMO_NOT_YET_IN_USE,
    promoErrorCodes.PROMO_USAGE_COUNT_EXCEEDED,
    promoErrorCodes.PROMO_EXPIRED,
    promoErrorCodes.PROMO_DOES_NOT_EXIST,
];

export const pagesToStripPromoQueryStringFrom = ["quote", "payment-method"];

export function getPromosWithAddableVaps(promoList) {
    if (promoList == null || !promoList.length) {
        return [];
    }

    return promoList.filter((promo) => addableVapsPromoPartNumbers.includes(promo.partNumber));
}

export function getLineItemsThatMatchPromos(promos, availableLineItems) {
    if (promos == null || availableLineItems == null) {
        return [];
    }
    const matchingLineItems = [];
    promos.forEach((promo) => {
        if (promo.discountedLineItemIds == null) {
            return;
        }
        promo.discountedLineItemIds.forEach((discountedLineItemId) => {
            matchingLineItems.push(
                ...availableLineItems.filter((lineItem) => lineItem.id === discountedLineItemId)
            );
        });
    });
    return matchingLineItems;
}

// Note: Addable VAPs for the Promo Validation endpoint can also be on the order already
//         There is no need to only send un-added VAPs in this array.
export function getAddableVapsFromAvailableLineItems(availableLineItems) {
    const addableVaps = [];
    addableVaps.push(...findLineItemsWithPartType(partTypeStrings.FRONT_WIPER, availableLineItems));
    addableVaps.push(
        ...findLineItemsWithPartType(partTypeStrings.RAIN_DEFENSE, availableLineItems)
    );
    return addableVaps;
}

export function removeVapsPromosFromPromoArray(promoArray) {
    if (!promoArray) {
        return [];
    }
    const filteredPromoArray = promoArray.filter((promo) => {
        return (
            promo.partNumber != promoPartNumberStrings.WIPER_DISCOUNT_PART_NUMBER &&
            promo.partNumber != promoPartNumberStrings.RAIN_DEFENSE_DISCOUNT_PART_NUMBER &&
            promo.partNumber != promoPartNumberStrings.GLASS_CLEANER_DISCOUNT_PART_NUMBER
        );
    });
    return filteredPromoArray;
}

export async function revalidatePromosAndValidateQueryStringPromo(
    newPromo,
    pricedLineItems,
    pageNameToLog,
    useDefaultCashParentAccount = false
) {
    const hasActivePromos = store.getters.order.lineItems.promos;
    const hasInactivePromos = store.getters.payment.inactivePromos;
    const hasNewPromo = !!newPromo;
    const addableVaps = getAddableVapsFromAvailableLineItems(pricedLineItems);

    let validatePromoResponse = null;
    let revalidatePromoResponse = null;

    if (
        (hasActivePromos && hasActivePromos.length) ||
        (hasInactivePromos && hasInactivePromos.length)
    ) {
        revalidatePromoResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
            storeActions.REVALIDATE_ORDER_PROMOS_AND_SAVE_SERVER_DATA,
            { useDefaultCashParentAccount },
            pageNameToLog,
            false
        );

        const revalidationErrorPromoCodes = revalidatePromoResponse.errors.map((x) => x.promoCode);
        baseMixin.methods.dispatchStoreAction(
            storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
            {
                activePromos: revalidatePromoResponse.promoLineItems,
                inactivePromos: revalidationErrorPromoCodes,
            },
            false
        );
    }

    if (hasNewPromo) {
        validatePromoResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
            storeActions.VALIDATE_ORDER_PROMO_AND_SAVE_SERVER_DATA,
            {
                promoCode: newPromo,
                addableVaps: addableVaps,
            },
            pageNameToLog,
            false
        );

        if (validatePromoResponse.errorCode == null) {
            // Save promo to store
            const activePromos = store.getters.order.lineItems.promos ?? [];
            activePromos.push(...validatePromoResponse.orderPromos);
            baseMixin.methods.dispatchStoreAction(
                storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
                {
                    activePromos: activePromos,
                },
                false
            );
        } else if (!excludeFromInactivePromoErrorCodes.includes(validatePromoResponse.errorCode)) {
            // Save any valid query string promoCode (not applied) to inactivePromos
            //  in order to not lose the query string promoCode if back button is pressed
            const activePromoCodes =
                store.getters.order.lineItems.promos?.map((promoObject) => {
                    return promoObject.promoCode.toUpperCase();
                }) ?? [];
            const isErrorPromoAlreadyOnOrder = activePromoCodes.includes(
                validatePromoResponse.promoCode.toUpperCase()
            );
            // Do not save an inactivePromo if it is already active on the order
            if (!isErrorPromoAlreadyOnOrder) {
                const inactivePromoCodes = store.getters.payment.inactivePromos ?? [];
                if (!inactivePromoCodes.includes(newPromo)) {
                    inactivePromoCodes.push(newPromo);
                    baseMixin.methods.dispatchStoreAction(
                        storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
                        {
                            inactivePromos: inactivePromoCodes,
                        },
                        false
                    );
                }
            }
        }
    }
    return { validatePromoResponse, revalidatePromoResponse };
}

export function buildToastMessagesFromRevalidateOrValidatePromoResponse(
    promoResponse,
    oldActivePromos = [],
    oldInactivePromos = []
) {
    if (promoResponse == null) {
        return [];
    }
    const alerts = [];
    // Revalidate always has an "errors" array
    if (promoResponse.errors) {
        const oldPromoCodes = oldActivePromos.map((promo) =>
            getPromoCodeWithoutBundleIdentifier(promo.promoCode)
        );
        const activePromoCodesFromResponse = getPromoCodesFromPromoObjectsWithoutDuplicates(
            promoResponse.promoLineItems
        );
        const newlyActivatedPromoCodes = activePromoCodesFromResponse.filter(
            (newPromoCode) => !oldPromoCodes.includes(newPromoCode)
        );

        newlyActivatedPromoCodes.forEach((newlyActivatedPromoCode) => {
            alerts.push(createPromoSuccessAlert(newlyActivatedPromoCode));
        });

        const newlyInactivatedPromos = getNewlyInactivatedPromos(
            oldInactivePromos,
            promoResponse.errors
        );
        newlyInactivatedPromos.forEach((newlyInactivatedPromo) => {
            alerts.push(createPromoErrorAlert(newlyInactivatedPromo));
        });
    }
    // validate response
    else {
        if (promoResponse.orderPromos) {
            const promoCodeToDisplay = getPromoCodeWithoutBundleIdentifier(
                promoResponse.orderPromos[0].promoCode
            );
            alerts.push(createPromoSuccessAlert(promoCodeToDisplay));
        } else {
            if (promoResponse.promoCode) {
                alerts.push(
                    createPromoErrorAlert(
                        promoResponse.promoCode,
                        promoResponse.errorCode,
                        promoResponse.additionalInfo
                    )
                );
            }
        }
    }
    return alerts;
}

export function getPromosThatMatchLineItemsOnOrder(promos, lineItemsOnOrder) {
    if (promos == null || lineItemsOnOrder == null) {
        return [];
    }
    const matchingPromoCodes = [];
    const matchingPromos = [];
    const consolidatedPromosWithIds = {};
    // Combine bundle promos to make sure each part of the bundle is satisfied
    promos.forEach((promo) => {
        const cleanPromoCode = getPromoCodeWithoutBundleIdentifier(promo.promoCode);
        if (consolidatedPromosWithIds[cleanPromoCode]) {
            consolidatedPromosWithIds[cleanPromoCode].push(...promo.discountedLineItemIds);
        } else {
            consolidatedPromosWithIds[cleanPromoCode] = promo.discountedLineItemIds.slice(0);
        }
    });
    Object.keys(consolidatedPromosWithIds).forEach((promoCode) => {
        let allIdsMatch = true;
        consolidatedPromosWithIds[promoCode].forEach((id) => {
            if (lineItemsOnOrder.filter((x) => x.id === id).length === 0) {
                allIdsMatch = false;
                return;
            }
        });
        if (allIdsMatch) {
            matchingPromoCodes.push(promoCode);
        }
    });
    promos.forEach((promo) => {
        const cleanPromoCode = getPromoCodeWithoutBundleIdentifier(promo.promoCode);
        if (matchingPromoCodes.includes(cleanPromoCode)) {
            matchingPromos.push(promo);
        }
    });
    return matchingPromos;
}

// Returns the vaps that the provided <promos> requires to be on the order to satisfy
//  Items that are already in <lineItemsOnOrder> will not be returned
export function getVapsThatNeedToBeAddedToSatisfyPromos(
    promos,
    availableLineItems,
    lineItemsOnOrder
) {
    const clonedVaps = deepClone(lineItemsOnOrder?.vaps ?? []);
    const promosWithAddableVaps = getPromosWithAddableVaps(promos);
    if (promosWithAddableVaps.length) {
        const matchingLineItems = getLineItemsThatMatchPromos(
            promosWithAddableVaps,
            availableLineItems
        );
        // Check if matching items are already in the order
        const idsOfVapsAlreadyInOrder = clonedVaps.map((lineItem) => lineItem.id);
        const vapsToAddToCart = matchingLineItems.filter(
            (lineItem) => !idsOfVapsAlreadyInOrder.includes(lineItem.id)
        );
        return vapsToAddToCart;
    } else {
        return [];
    }
}

export function removeCurrentlyActivePromoCodesFromInactivePromos(
    activePromoObjects,
    inactivePromos
) {
    activePromoObjects = activePromoObjects ?? [];
    inactivePromos = inactivePromos ?? [];
    const activePromoCodes = activePromoObjects.map((promoObject) =>
        getPromoCodeWithoutBundleIdentifier(promoObject.promoCode).toUpperCase()
    );
    return inactivePromos.filter((inactivePromo) => {
        return !activePromoCodes.includes(inactivePromo.toUpperCase());
    });
}

// bundle promos look like: "bundlePromo/###"" for each promo, this returns "bundlePromo" only
export function getPromoCodeWithoutBundleIdentifier(promoCode) {
    return promoCode.split("/")[0];
}

export function shouldStripPromoQueryString(fmgPageQueryValue) {
    return pagesToStripPromoQueryStringFrom.includes(fmgPageQueryValue);
}

export function getNewlyInactivatedPromos(oldInactivePromos, newInactivePromos) {
    const inactivePromoCodesFromResponse =
        getPromoCodesFromPromoObjectsWithoutDuplicates(newInactivePromos);
    return inactivePromoCodesFromResponse.filter(
        (errorPromoCode) => !oldInactivePromos?.includes(errorPromoCode)
    );
}

export function createPromoSuccessAlert(promoCode) {
    return {
        messageHeadline: "Promo code applied!",
        messageCopy: `Promo code ${getPromoCodeWithoutBundleIdentifier(
            promoCode.toUpperCase()
        )} was successfully applied to your cart.`,
        type: "alert-success",
        isDismissible: true,
        shouldAutoFade: true,
    };
}

export function createPromoErrorAlert(promoCode, errorCode = null, additionalInfo) {
    const alert = {
        messageHeadline: "Promo code error",
        type: "alert-danger",
        shouldAutoFade: false,
        isDismissible: true,
    };
    if (stackingPromoErrorCodes.includes(errorCode)) {
        alert.messageCopy = `Sorry, promo code ${getPromoCodeWithoutBundleIdentifier(
            promoCode.toUpperCase()
        )} cannot be combined with ${additionalInfo[0].toUpperCase()}`;
    } else {
        alert.messageCopy = `Sorry, promo code ${getPromoCodeWithoutBundleIdentifier(
            promoCode.toUpperCase()
        )} is not valid`;
    }
    return alert;
}

// returns a list of promo codes (string) only without duplicates or bundle identifiers
//  <promos> parameter must be an array of objects with a 'promoCode' field
export function getPromoCodesFromPromoObjectsWithoutDuplicates(promos) {
    const promoCodes = [];
    if (!promos) {
        return promoCodes;
    }
    promos.forEach((promo) => {
        const promoCodeToDisplay = getPromoCodeWithoutBundleIdentifier(promo.promoCode);
        if (!promoCodes.includes(promoCodeToDisplay)) {
            promoCodes.push(promoCodeToDisplay);
        }
    });

    return promoCodes;
}

// private methods
function findLineItemsWithPartType(typeToFind, itemsToSearch) {
    const partTypeMatches =
        itemsToSearch?.filter(
            (lineItem) => lineItem.partType.toUpperCase() === typeToFind.toUpperCase()
        ) ?? [];
    return partTypeMatches;
}
