/**
 * Variants
 * @module Variants
 * @exports Variants
 * @type {{init: (function(*): Variants)}}
 */

import {
    Variant,
    ProductVariant,
    Galleries,
    VariantsOptions,
    VariantStatistic,
    ProductVariants,
    ProductSkuDetail,
    BulkPriceData
} from './types';

class Variants {
    variants: Variant[];
    productVariants: ProductVariant[];
    galleries: Galleries;
    bulkPriceData: BulkPriceData;
    variantStatistic: VariantStatistic;
    products: ProductVariants;
    variantByOption: { [key: number]: number };
    groupVariants: Variant[];
    currentProductVariant: ProductVariant | null;

    constructor(options: VariantsOptions) {
        this.variants = options.variants;
        this.productVariants = options.productVariants;
        this.galleries = options.galleries;
        this.bulkPriceData = options.bulkPriceData;
        this.variantStatistic = this.buildVariantStatistic(options.variants, options.productVariants);
        this.products = this.buildProductVariants(options.productVariants);
        this.variantByOption = this.buildVariantByOption(options.variants);
        this.groupVariants = this.buildGroupVariants(options.variants);
        this.currentProductVariant = null;
    }

    private rebuildVariants(
        variants: Variant[],
        currentProductVariant: ProductVariant,
        variantsStatistic: VariantStatistic,
        variantByOption: { [key: number]: number },
        products: ProductVariants,
        groupVariants: Variant[]
    ) {
        const groupVariantsCopy = groupVariants.map(variant => ({
            ...variant,
            values: [...variant.values]
        }));

        for (let index = 0; index < variants.length; index++) {
            const variant = variants[index];
            let variantKeyMap = new Map();
            currentProductVariant.variants.forEach(id => {
                variantKeyMap.set(variantByOption[id], id);
            })
            let otherVariants = currentProductVariant.variants.filter(id => variantByOption[id] != variant.id);
            otherVariants.forEach(id => {
                variantKeyMap.set(variantByOption[id], id);
            });
            let optionGroup: { id: number; variant_id?: number; variant_slug?: string; }[] = [];
            variant.values.forEach(item => {
                let tmpKeyMap = new Map(variantKeyMap);
                tmpKeyMap.set(variant.id, item.id);
                let itemVariantKey = [...tmpKeyMap.values()].join('-');
                let prefixVariant = '';
                let variantIsOk = true;
                if (variant.slug == 'style' && index >= 0) {
                    if (otherVariants && otherVariants.length >= 2) {
                        prefixVariant = otherVariants[0] + '-' + item.id;
                        variantIsOk = false;
                        if (prefixVariant && variantsStatistic) {
                            if (variantsStatistic[prefixVariant]) {
                                variantIsOk = true;
                            } else if (variantsStatistic[item.id + '-' + otherVariants[0]]) {
                                variantIsOk = true;
                            }
                        }
                        if (index == 0 && !variantIsOk && variantsStatistic[item.id]) {
                            variantIsOk = true;
                        }
                    }
                }
                if (variantIsOk && ((products.productByUniqId[itemVariantKey] && products.productByUniqId[itemVariantKey].id) || variant.show_invalid)) {
                    optionGroup.push(item);
                } else if (variantIsOk && variant.show_invalid_above) {
                    let aboveKey = currentProductVariant.variants.filter((id, idx) => idx < index).join('-');
                    if (products.productByUniqIdAbove[aboveKey]) {
                        optionGroup.push(item);
                    }
                }
            });
            for (const groupVariant of groupVariantsCopy) {
                if (groupVariant.id == variant.id) {
                    groupVariant.values = optionGroup;
                }
            }
        }

        this.groupVariants = groupVariantsCopy;
    }

    private buildVariantStatistic (variants: Variant[], productVariants: ProductVariant[]): VariantStatistic {
        let variantsStatistic: { [key: string]: {id: number, sku_key: string, count: number, price: number, high_price: number} } = {};
        if (variants.length < 2) {
            return variantsStatistic;
        }

        let firstOptionIndex = 0;
        for (let key in variants) {
            if (variants[key].type == 'OPTION') {
                firstOptionIndex = parseInt(key);
                break;
            }
        }

        for (let item of productVariants) {
            let vKeyArr = item.variants.filter((value, index) => index <= firstOptionIndex).map(item => item);
            for (let i = firstOptionIndex + 1; i < item.variants.length; i++) {
                let vKey = vKeyArr.join('-') + '-' + item.variants[i];
                if (!variantsStatistic[vKey]) {
                    variantsStatistic[vKey] = {
                        id: item.id,
                        sku_key: item.variants.join('-'),
                        count: 1,
                        price: item.price,
                        high_price: item.high_price
                    }
                } else {
                    variantsStatistic[vKey].count++;
                    if (item.price < variantsStatistic[vKey].price) {
                        variantsStatistic[vKey].id = item.id;
                        variantsStatistic[vKey].sku_key = item.variants.join('-');
                        variantsStatistic[vKey].price = item.price;
                        variantsStatistic[vKey].high_price = item.high_price;
                    }
                }
            }

            let vKey = vKeyArr.join('-');

            if (!variantsStatistic[vKey]) {
                variantsStatistic[vKey] = {
                    id: item.id,
                    sku_key: item.variants.join('-'),
                    count: 1,
                    price: item.price,
                    high_price: item.high_price
                }
            } else {
                variantsStatistic[vKey].count++;
                if (item.price < variantsStatistic[vKey].price) {
                    variantsStatistic[vKey].id = item.id;
                    variantsStatistic[vKey].sku_key = item.variants.join('-');
                    variantsStatistic[vKey].price = item.price;
                    variantsStatistic[vKey].high_price = item.high_price;
                }
            }
        }

        return variantsStatistic;
    }

    private buildProductVariants(productVariants: ProductVariant[]): ProductVariants {
        const productById: { [key: number]: {id: number, sku: string, price: number, high_price: number, variants: number[]} } = {};
        const productByUniqId: { [key: string]: any } = {};
        const productByUniqIdAbove: { [key: string]: any } = {};

        if (productVariants) {
            productVariants.forEach(function(item) {
                productById[item.id] = {
                    id: item.id,
                    sku: item.sku,
                    price: item.price,
                    high_price: item.high_price,
                    variants: item.variants
                };
                const variantOptionIds = item.variants;
                if (variantOptionIds.length > 0) {
                    const key = variantOptionIds.join("-");
                    productByUniqId[key] = item;
                    if (item.variants.length > 2) {
                        for (let j = 1; j < variantOptionIds.length - 2; j++) {
                            let aboveKey = variantOptionIds.slice(0, j).join('-');
                            productByUniqIdAbove[aboveKey] = item;
                        }
                    }
                }
            });
        }

        return {
            productById: productById,
            productByUniqId: productByUniqId,
            productByUniqIdAbove: productByUniqIdAbove
        };
    }

    private buildVariantByOption(variants: Variant[]): { [key: number]: number } {
        let variantByOption: { [key: number]: number } = {};
        let optionById: { [key: number]: any } = {};
        variants.forEach(variant => {
            variant.values.forEach(item => {
                variantByOption[item.id] = variant.id
                item.variant_id = variant.id;
                item.variant_slug = variant.slug;
                optionById[item.id] = item;
            })
        });

        return variantByOption;
    }

    private buildGroupVariants(variants: Variant[]): Variant[] {
        variants.forEach((element, index) => {
            element.show_invalid = index == 0;
            if (variants[0].type != 'OPTION') {
                if (index <= variants.length - 3) {
                    element.show_invalid = true;
                }
                if (index <= variants.length - 2 && element.type != "IMAGE") {
                    element.show_invalid_above = true;
                }
            } else {
                if (index && index == variants.length - 2 && element.type == "OPTION") {
                    element.show_invalid_above = true;
                }
                if (index == 1 && variants.length > 2 && element.type == "OPTION") {
                    element.show_invalid = true;
                }
            }
        });

        return variants;
    }

    private isSelectedVariantValue(variantValueId: number): boolean {
        if (!this.currentProductVariant || !this.currentProductVariant.variants) {
            return false;
        }

        let isExists = this.currentProductVariant.variants.find(id => id == variantValueId);

        return !!isExists;
    }

    private getPriceVariant(option: {
        id: number;
        variant_id?: number;
        variant_slug?: string;
        is_selected?: boolean,
        name?: string,
        slug?: string,
        price?: number,
        high_price?: number,
    }, groupId: number, quantity: number): number {
        let result = 0;
        if (this.variants.length > 0) {
            if (!this.currentProductVariant) {
                return result;
            }

            let isStyleFirstVariant = this.variants[0].slug == 'style';
            let listIndex: (number | string)[] = [];
            let variantKeyMap = new Map();
            this.currentProductVariant.variants.forEach((optionId: any) => {
                if (this.variantByOption[optionId] != groupId) {
                    variantKeyMap.set(this.variantByOption[optionId], optionId);
                    listIndex.push(optionId);
                } else {
                    variantKeyMap.set(this.variantByOption[option.id], option.id);
                }
            });
            let key = [...variantKeyMap.values()].join('-');
            let currentProductBySpid = this.products.productById[this.currentProductVariant.id];
            if (key in this.products.productByUniqId) {
                result = this.products.productByUniqId[key].price;
            } else {
                let prefixVariant = '';
                let variantIsOk = true;
                if (listIndex && listIndex.length >= 2) {
                    prefixVariant = listIndex[0] + '-' + option.id;
                    variantIsOk = false;
                    if (this.currentProductVariant && this.currentProductVariant.id) {
                        let keyWithColor = currentProductBySpid.variants
                            .filter(id => this.variantByOption[id] != 2)
                            .map(id => this.variantByOption[id] == this.variantByOption[option.id] ? option.id : id).join('-');
                        if (this.variantStatistic[keyWithColor]) {
                            variantIsOk = true;
                            prefixVariant = keyWithColor;
                        }
                    }

                    if (prefixVariant && this.variantStatistic) {
                        if (this.variantStatistic[prefixVariant]) {
                            variantIsOk = true;
                        } else if (this.variantStatistic[option.id + '-' + listIndex[0]]) {
                            variantIsOk = true;
                            prefixVariant = option.id + '-' + listIndex[0];
                        }

                        if (!variantIsOk && isStyleFirstVariant && this.variantStatistic[option.id]) {
                            prefixVariant = option.id.toString();
                            variantIsOk = true;
                        }
                    }
                }

                if (variantIsOk && key) {
                    if (prefixVariant && this.variantStatistic && this.variantStatistic[prefixVariant]) {
                        result = this.variantStatistic[prefixVariant].price;
                    }
                }

                if (!result) {
                    // find index of option in variants
                    const index = this.variants.findIndex(v => v.id === groupId);
                    // append option id to the index of listIndex
                    listIndex.splice(index, 0, option.id);
                    prefixVariant = listIndex.join('-');

                    if (this.variantStatistic && this.variantStatistic[prefixVariant]) {
                        result = this.variantStatistic[prefixVariant].price;
                    }
                }

                // apply bulk price
                if (this.variantStatistic && this.variantStatistic[prefixVariant]) {
                    const productSkuId = this.variantStatistic[prefixVariant].id;
                    if (this.bulkPriceData[productSkuId]) {
                        const bulkPriceDataBySkuId = this.bulkPriceData[productSkuId];
                        const validBulkPriceItems = bulkPriceDataBySkuId.filter((item) => {
                            return item.min_quantity <= quantity;
                        });
                        if (validBulkPriceItems.length > 0) {
                            const validBulkPriceItem = validBulkPriceItems.pop();
                            if (validBulkPriceItem) {
                                result = validBulkPriceItem.price;
                            }
                        }
                    }
                }
            }
        }

        return result;
    }

    getPriceVariantV2(option: {
        id: number;
        variant_id?: number;
        variant_slug?: string;
        is_selected?: boolean,
        name?: string,
        slug?: string,
        price?: number,
        high_price?: number,
    }, groupId: number, quantity: number, isBulkOrderItem = true) : number {
        let result = 0;
        const currentValueId = option.id;
        const currentProductVariant = this.currentProductVariant;
        if (!currentProductVariant || this.variants.length === 0) {
            return result;
        }

        // build list variant value ids
        let valueIds: number[] = [];
        this.groupVariants.forEach((variant) => {
            variant.values.forEach((value) => {
                if (value.is_selected) {
                    valueIds.push(value.id);
                }
            });
        });

        const productSkuId = this.getSkuIdByValueIds(valueIds, currentValueId);
        const productVariant = this.productVariants.find(v => v.id === productSkuId);
        if (!productVariant) {
            return result;
        }

        result = productVariant.price;
        if (this.bulkPriceData[productSkuId] && isBulkOrderItem) {
            const bulkPriceDataBySkuId = this.bulkPriceData[productSkuId];
            const validBulkPriceItems = bulkPriceDataBySkuId.filter((item) => {
                return item.min_quantity <= quantity;
            });
            if (validBulkPriceItems.length > 0) {
                const validBulkPriceItem = validBulkPriceItems.pop();
                if (validBulkPriceItem) {
                    result = validBulkPriceItem.price;
                }
            }
        }

        return result;
    }

    getProductSkuDetail(productSkuId: number, quantity: number, isBulkOrderItem = true): ProductSkuDetail {
        if (!this.variants) {
            throw new Error('Variants Data not set');
        }

        if (!this.productVariants) {
            throw new Error('Product Variants Data not set');
        }

        if (!this.galleries) {
            throw new Error('Galleries not set');
        }

        this.currentProductVariant = this.productVariants.find(v => v.id === productSkuId) || null;

        if (!this.currentProductVariant) {
            throw new Error(`Product SKU ${productSkuId} not found`);
        }

        this.rebuildVariants(
            this.variants,
            this.currentProductVariant,
            this.variantStatistic,
            this.variantByOption,
            this.products,
            this.groupVariants
        );

        let galleries = this.galleries[this.currentProductVariant.id] ?? [];

        let price = this.currentProductVariant.price;
        let highPrice = this.currentProductVariant.high_price;
        // apply bulk price
        if (this.bulkPriceData[productSkuId] && isBulkOrderItem) {
            const bulkPriceDataBySkuId = this.bulkPriceData[productSkuId];
            const validBulkPriceItems = bulkPriceDataBySkuId.filter((item) => {
                return item.min_quantity <= quantity;
            });
            if (validBulkPriceItems.length > 0) {
                const validBulkPriceItem = validBulkPriceItems.pop();
                if (validBulkPriceItem) {
                    price = validBulkPriceItem.price;
                }
            }
        }

        for (const variant of this.groupVariants) {
            for (const value of variant.values) {
                value.is_selected = this.isSelectedVariantValue(value.id);
                if (value.is_selected) {
                    variant.current_value_id = value.id;
                    variant.current_value_name = value.name;
                }
            }
        }

        for (const variant of this.groupVariants) {
            if (variant.type === 'OPTION') {
                for (const value of variant.values) {
                    value.price = this.getPriceVariantV2(value, variant.id, quantity, isBulkOrderItem);
                }
            }
        }

        return {
            product: this.currentProductVariant,
            variants: this.groupVariants,
            galleries: galleries,
            price: price,
            high_price: highPrice
        }
    }

    getSkuIdByValueIds(valueIds: number[], selectedValueId: number): number {
        const getMatchCount = (source: any, target: any) => {
            let matchCount = 0;
            for (let i = 0; i < source.length; i++) {
                if (source[i] === target[i]) {
                    matchCount++;
                }
            }

            return matchCount;
        }

        let productVariantMatch = null
        let highestMatch = -1;
        for (const productVariant of this.productVariants) {
            if (!productVariant.variants.includes(selectedValueId)) {
                continue;
            }

            const currentMatches = getMatchCount(valueIds, productVariant.variants);
            if (currentMatches > highestMatch) {
                highestMatch = currentMatches;
                productVariantMatch = productVariant;
            }
        }

        if (!productVariantMatch) {
            productVariantMatch = this.productVariants[0];
        }

        return productVariantMatch ? productVariantMatch.id : 0;
    }

    applyBulkPrice(productSkuDetail: ProductSkuDetail, quantity: number): void {
        const productSkuId = productSkuDetail.product.id;
        if (this.bulkPriceData[productSkuId]) {
            const bulkPriceDataBySkuId = this.bulkPriceData[productSkuId];
            const validBulkPriceItems = bulkPriceDataBySkuId.filter((item) => {
                return item.min_quantity <= quantity;
            });
            if (validBulkPriceItems.length > 0) {
                const validBulkPriceItem = validBulkPriceItems.pop();
                if (validBulkPriceItem) {
                    productSkuDetail.price = validBulkPriceItem.price;
                }
            } else if (this.currentProductVariant) {
                productSkuDetail.price = this.currentProductVariant.price;
            }
        }

        for (const variant of productSkuDetail.variants) {
            if (variant.type === 'OPTION') {
                for (const value of variant.values) {
                    value.price = this.getPriceVariantV2(value, variant.id, quantity);
                }
            }
        }
    }

    getBulkPriceText(productSkuDetail: ProductSkuDetail, quantity: number): { text: string, price?: string, quantity?: number } {
        let buyMoreByQuantityText = '#price each for #quantity items';
        let spid = productSkuDetail.product.id;
        if (this.bulkPriceData[spid]) {
            let price = 0;
            let minQuantity = 0;
            const bulkPriceDataBySkuId = this.bulkPriceData[spid];
            const validBulkPriceItems = bulkPriceDataBySkuId.filter((item) => {
                return item.min_quantity >= quantity;
            });
            if (validBulkPriceItems.length > 0) {
                let validBulkPriceItem = validBulkPriceItems.reduce((closest, current) => {
                    return current.min_quantity < closest.min_quantity ? current : closest;
                }, validBulkPriceItems[0]);
                if (validBulkPriceItem) {
                    price = validBulkPriceItem.price;
                    minQuantity = validBulkPriceItem.min_quantity;

                    return {
                        text: buyMoreByQuantityText,
                        price: price.toFixed(2),
                        quantity: minQuantity
                    };
                }
            } else {
                let validBulkPriceItem = this.bulkPriceData[spid].reduce((closest, current) => {
                    return current.min_quantity > closest.min_quantity ? current : closest;
                }, this.bulkPriceData[spid][0]);
                if (validBulkPriceItem) {
                    price = validBulkPriceItem.price;
                    minQuantity = validBulkPriceItem.min_quantity;

                    return {
                        text: buyMoreByQuantityText,
                        price: price.toFixed(2),
                        quantity: minQuantity
                    };
                }
            }
        }

        return {
            text: 'Buying In Bulk?'
        }
    }
}

export default Variants;