import React, { useState } from 'react';
import PropTypes from 'prop-types';
import merge from 'deepmerge';
import { createNamespace } from '../../tools/namingTools';
import { useContainerWidth } from '../../tools/widths/useContainerWidth';
import { LEFT, RIGHT, TOP } from '../../constants/alignment';
import { LAYOUT_HORIZONTAL, LAYOUT_VERTICAL } from '../../constants/layouts';
import { DROPDOWN, INPUT } from '../../constants/schemaComponentTypes';
import { bMedium } from '../../constants/breakpoints';
import { NAMESPACES } from '../../constants/namespaces';
import { SPECIAL_CHARACTERS_BLOCKLIST } from '../../constants/regexConstants';
import { SORT_LATEST, SORT_OLDEST, SORT_HIGH, SORT_LOW, sortingParams } from '../../components/product-reviews/const';
import Rating from '../rating/Rating';
import Button from '../button/Button';
import ButtonIcon from '../button-icon/ButtonIcon';
import Select from '../select/Select';
import GridContainer from '../grid-container/GridContainer';
import ProductReviewDetail, { reviewItemShape, reviewDetailSchemaForm, DEFAULT_AVATAR_SIZE as logoSize, DEFAULT_AVATAR_SPACING as logoSpacing } from '../../components/product-review-detail/ProductReviewDetail';
import ProductFeaturedQuotes, { productQuoteShape } from '../../components/product-featured-quotes/ProductFeaturedQuotes';
import './styles/ProductReviews.scss';

const n = createNamespace('ProductReviews');

export const PAGE_SIZE = 10;
const ProductReviews = ({
    data: {
        items,
        featured,
        totalCount,
        totalCustomerReviews,
        averageRating,
        ratings,
        isLoggedIn,
        addReviewUrl,
        pageInfo,
        i18n,
        fetchMore,
        setPaginatedData,
        refetch
    },
    settings
}) => {
    const settingsWithDefaults = { ...ProductReviews.defaultProps.settings, ...settings };
    const i18nWithDefaults = i18n
        ? merge(ProductReviews.defaultProps.data.i18n, i18n)
        : ProductReviews.defaultProps.data.i18n;

    const [containerRef, containerWidth] = useContainerWidth();
    const isMobile = containerWidth && containerWidth < bMedium;

    const sortingOptions = [
        {
            value: SORT_LATEST,
            selected: true,
            label: i18nWithDefaults.labels.latest
        },
        {
            value: SORT_OLDEST,
            selected: false,
            label: i18nWithDefaults.labels.oldest
        },
        {
            value: SORT_HIGH,
            selected: false,
            label: i18nWithDefaults.labels.highestRated
        },
        {
            value: SORT_LOW,
            selected: false,
            label: i18nWithDefaults.labels.lowestRated
        }
    ];

    const hasRatings = Boolean(ratings && ratings.length);

    const totalReviews = hasRatings && ratings.reduce((total, rating) => ({
        count: total.count + rating.count,
        highestCount: Math.max(rating.count, total.highestCount),
        totalPoints: total.totalPoints + (rating.count * rating.rating)
    }), {
        highestCount: 0
    });

    const hasPagination = pageInfo && pageInfo.hasNextPage;
    const sortByInitialValue = sortingOptions && sortingOptions.find(o => o.selected);
    const [sortBy, setSortBy] = useState(sortByInitialValue && sortByInitialValue.value);
    // We need displayedReviewsCount state in order to have a new key for load more button
    // so React creates and inserts load more button in the DOM on each render.
    // Thus new reviews scroll down to the page.
    const [displayedReviewsCount, setDisplayedReviewsCount] = useState(PAGE_SIZE);
    const [ratingFilter, setRatingFilter] = useState(null);

    const getCurrentPage = () => i18nWithDefaults.labels.currentPage && i18nWithDefaults.labels.currentPage.replace(
        '{end}', displayedReviewsCount <= totalCount ? displayedReviewsCount : totalCount
    ).replace(
        '{total}', totalCount
    );

    const writeReview = () => {
        if (!isLoggedIn) {
            window.location = `/login?redirectUrl=${addReviewUrl}`;
        } else {
            window.location = addReviewUrl;
        }
    };

    const loadMoreReviews = e => {
        e.preventDefault();
        fetchMore({
            variables: {
                first: PAGE_SIZE,
                after: pageInfo.endCursor
            }
        }).then(response => {
            setPaginatedData(response.data);
            setDisplayedReviewsCount(displayedReviewsCount + PAGE_SIZE);
        });
    };

    const onSortChange = e => {
        const sortingOption = e.value;
        setSortBy(sortingOption);
        setDisplayedReviewsCount(PAGE_SIZE);

        if (refetch) {
            refetch({
                first: PAGE_SIZE,
                orderByField: sortingParams[sortingOption].orderByField,
                orderByDirection: sortingParams[sortingOption].orderByDirection
            });
        }
    };

    const onFilterByRating = rating => {
        setRatingFilter(rating);
        setDisplayedReviewsCount(PAGE_SIZE);

        if (refetch) {
            refetch({
                first: PAGE_SIZE,
                filterByRating: rating,
                reviewFilter: {
                    rating: {
                        operation: 'EQUALS',
                        value: rating
                    }
                }
            });
        }
    };

    const clearFilter = () => {
        setRatingFilter(null);
        setDisplayedReviewsCount(PAGE_SIZE);
        setSortBy(SORT_LATEST);

        if (refetch) {
            refetch({
                first: PAGE_SIZE,
                orderByField: sortingParams.LATEST.orderByField,
                orderByDirection: sortingParams.LATEST.orderByDirection,
                reviewFilter: null
            });
        }
    };

    const getRatingCountWidth = () => {
        let width = null;
        const reviewCountList = ratings && ratings.length ? ratings.map(item => item.count) : [];

        if (reviewCountList.length > 1) {
            const biggestCountValue = Math.max.apply(null, reviewCountList);

            width = `${biggestCountValue.toString().length}ch`;
        }

        return width;
    };

    const reviewsBlock = items && items.length ? (
        <React.Fragment>
            <h3 {...n('title', 'hidden').props}>{i18nWithDefaults.labels.customerReviews}</h3>
            <div {...n('header').props}>
                <div {...n('filter').props}>
                    <div {...n('paging').props}>{getCurrentPage()}</div>
                    {ratingFilter && (
                        <div {...n('clearFilter').props}>
                            {ratingFilter}
                            {' '}
                            {ratingFilter > 1 ? i18nWithDefaults.labels.multiStars
                                : i18nWithDefaults.labels.singleStar}
                            <ButtonIcon
                                {...n('btnClearFilter').withTestId().props}
                                type="link"
                                theme="primary"
                                onClick={clearFilter}
                            >
                                {i18nWithDefaults.labels.clearFilter}
                            </ButtonIcon>
                        </div>
                    )}
                </div>
                <div {...n('sort').props}>
                    <Select
                        name="sort"
                        id="sort"
                        value={sortBy}
                        label={i18nWithDefaults.labels.sortBy}
                        onChange={onSortChange}
                        options={sortingOptions}
                        inline
                    />
                </div>
            </div>
            <div {...n('list').props}>
                {items.map(item => (
                    <div key={item.id} {...n('item').props}>
                        <ProductReviewDetail
                            key={item.id}
                            data={item}
                            settings={settingsWithDefaults}
                            i18n={i18nWithDefaults.labels}
                        />
                    </div>
                ))}
            </div>
            {hasPagination && (
                <div {...n('btnContainer').props}>
                    <Button
                        key={displayedReviewsCount}
                        type="secondary"
                        onClick={loadMoreReviews}
                        data-testid="button:pagination:moreReviews"
                    >
                        {settingsWithDefaults.moreReviewsLabel || i18nWithDefaults.labels.moreReviews}
                    </Button>
                </div>
            )}
        </React.Fragment>
    ) : (
        <span>{i18nWithDefaults.labels.noReviews}</span>
    );

    const basedOnReviewsBlock = (
        <span {...n('ratingTotalReview').withE2EId().props}>
            {i18nWithDefaults.labels.basedOnReviews && i18nWithDefaults.labels.basedOnReviews.replace('{n}', totalCustomerReviews)}
        </span>
    );


    const isRatingPositionTop = settingsWithDefaults.position === TOP.value;
    const ratingAverageProps = isRatingPositionTop && {
        starWidth: 30
    };

    const componentVariables = {
        'product-review-rating-count-width': getRatingCountWidth()
    };

    return (
        <section {...n('wrapper').withTestId().withE2EId().props} ref={containerRef}>
            <GridContainer {...n('gridContainer', `rating-${settingsWithDefaults.position}`).withVariables(componentVariables).props}>
                <div {...n('content').props}>
                    <header {...n('hgroup').props}>
                        <h2 {...n('title').withE2EId().props}>{i18nWithDefaults.labels.reviews}</h2>
                        {!isMobile && !isRatingPositionTop && (
                            <div {...n('hgroupBtnReview').withE2EId().props}>
                                <ButtonIcon theme="primary" onClick={writeReview}>{i18nWithDefaults.labels.writeReview}</ButtonIcon>
                            </div>
                        )}
                    </header>
                    {reviewsBlock}
                </div>
                <aside {...n('aside').props}>
                    <h3 {...n('title', 'hidden').props}>{i18nWithDefaults.labels.featuredReviews}</h3>
                    <div {...n('rating').props}>
                        <div {...n('ratingAverage').props}>
                            <div {...n('ratingAverageTotal', !averageRating && 'empty').props}>
                                <span {...n('ratingAverageTotalValue').props}>{averageRating && averageRating.toFixed(1)}</span>
                                {isRatingPositionTop && (
                                    <span>{i18nWithDefaults.labels.averageReview}</span>
                                )}
                            </div>
                            <div {...n('ratingAverageContent').withE2EId().props}>
                                <div {...n('ratingAverageStars').props}>
                                    <Rating
                                        rating={averageRating}
                                        displayRatingNumber={false}
                                        {...ratingAverageProps}
                                    />
                                </div>
                                {!isRatingPositionTop && basedOnReviewsBlock}
                            </div>
                        </div>
                        <div {...n('ratingStarList').props}>
                            {isRatingPositionTop && basedOnReviewsBlock}
                            {hasRatings && ratings.sort((a, b) => b.rating - a.rating).map(rating => {
                                const prc = (rating.count / totalReviews.highestCount) * 100;
                                const clickHandler = () => onFilterByRating(rating.rating);
                                return (
                                    <div
                                        key={`${rating.rating}-${rating.count}`}
                                        onClick={rating.count > 0 ? clickHandler : null}
                                        onKeyPress={rating.count > 0 ? clickHandler : null}
                                        role="button"
                                        tabIndex="0"
                                        {...n('ratingStarItem', rating.count === 0 && 'empty').props}
                                    >
                                        <Rating
                                            rating={rating.rating}
                                            displayRatingNumber={false}
                                            isSelected={ratingFilter === rating.rating}
                                        />
                                        <div {...n('ratingStarProgress').props}>
                                            <div {...n('ratingStarProgressBar', `rating-${rating.rating}`).props} style={{ width: `${prc}%` }} />
                                        </div>
                                        <span {...n('ratingStarCount').props}>
                                            {rating.count}
                                        </span>
                                    </div>
                                );
                            })}
                        </div>
                        <div {...n('ratingBtnReview').props}>
                            <ButtonIcon theme="primary" onClick={writeReview}>{i18nWithDefaults.labels.writeReview}</ButtonIcon>
                        </div>
                    </div>
                    {featured && featured.length ? (
                        <div {...n('quotes').props}>
                            <ProductFeaturedQuotes
                                data={{
                                    featured,
                                    i18n,
                                    layout: isRatingPositionTop ? LAYOUT_HORIZONTAL : LAYOUT_VERTICAL,
                                    logoSize,
                                    logoSpacing: isRatingPositionTop ? logoSpacing : null
                                }}
                            />
                        </div>
                    ) : null}
                </aside>
            </GridContainer>
        </section>
    );
};

export const POSITION_OPTIONS = [LEFT, RIGHT, TOP];
export const POSITION_OPTIONS_ENUM = POSITION_OPTIONS.map(({ value }) => value);

ProductReviews.schema = () => ({
    name: 'ProductReviews',
    title: 'uieditor.sfbComponent.productReviews.title',
    anchor: 'product-section-reviews',
    iconName: 'star',
    orderableSection: [NAMESPACES.profile],
    form: {
        position: {
            title: 'uieditor.sfbComponent.productReviews.position.title',
            type: DROPDOWN,
            defaultValue: RIGHT.value,
            options: POSITION_OPTIONS
        },
        moreReviewsLabel: {
            title: 'uieditor.sfbComponent.productReviews.moreReviewsLabel.title',
            placeholder: 'uieditor.sfbComponent.productReviews.moreReviewsLabel.defaultValue',

            type: INPUT,
            required: false,
            validation: {
                pattern: SPECIAL_CHARACTERS_BLOCKLIST,
                message: 'uieditor.sfbComponent.input.validation.message'
            }
        },
        ...reviewDetailSchemaForm
    },
    marketplaceData: {
        adTenant: 'config.channelSettings.partner',
        baseUrl: 'config.channelSettings.channelCanonicalBaseUrl',
        addReviewUrl: 'application.reviews.addReviewUrl',
        i18n: 'application.reviews.i18n',
        isLoggedIn: 'config.user.isLoggedIn',
        productId: 'application.config.uuid',
        featured: 'application.reviews.featured'
    }
});

ProductReviews.propTypes = {
    data: PropTypes.shape({
        items: PropTypes.arrayOf(
            PropTypes.shape(reviewItemShape)
        ),
        featured: PropTypes.arrayOf(PropTypes.shape(productQuoteShape)),
        isLoggedIn: PropTypes.bool,
        addReviewUrl: PropTypes.string,
        totalCount: PropTypes.number,
        totalCustomerReviews: PropTypes.number,
        averageRating: PropTypes.number,
        ratings: PropTypes.arrayOf(
            PropTypes.shape({
                rating: PropTypes.number,
                text: PropTypes.string,
                count: PropTypes.number
            })
        ),
        pageInfo: PropTypes.shape({
            endCursor: PropTypes.string,
            hasNextPage: PropTypes.bool
        }),
        i18n: PropTypes.shape({
            labels: PropTypes.shape({
                writeReview: PropTypes.string,
                basedOnReviews: PropTypes.string,
                currentPage: PropTypes.string,
                sortBy: PropTypes.string,
                latest: PropTypes.string,
                oldest: PropTypes.string,
                highestRated: PropTypes.string,
                lowestRated: PropTypes.string,
                reviews: PropTypes.string,
                moreReviews: PropTypes.string,
                noReviews: PropTypes.string,
                validatedReviewer: PropTypes.string,
                singleStar: PropTypes.string,
                multiStar: PropTypes.string,
                clearFilter: PropTypes.string,
                featuredReviews: PropTypes.string,
                customerReviews: PropTypes.string
            })
        }),
        fetchMore: PropTypes.func,
        setPaginatedData: PropTypes.func,
        refetch: PropTypes.func
    }),
    settings: PropTypes.shape({
        position: PropTypes.oneOf(POSITION_OPTIONS_ENUM),
        moreReviewsLabel: PropTypes.string,
        isAvatarVisible: PropTypes.bool
    })
};

const SETTINGS = ProductReviews.schema().form;

ProductReviews.defaultProps = {
    data: {
        items: null,
        featured: null,
        ratings: null,
        totalCount: 0,
        totalCustomerReviews: 0,
        averageRating: 0,
        pageInfo: null,
        i18n: {
            labels: {
                writeReview: 'Write a Review',
                reviews: 'Reviews',
                moreReviews: 'More Reviews',
                basedOnReviews: 'Based on {n} reviews',
                currentPage: '1-{end} of {total} reviews',
                noReviews: 'No customer reviews have been written.',
                sortBy: 'Sort by',
                latest: 'Most recent',
                oldest: 'Least recent',
                highestRated: 'Highest rated',
                lowestRated: 'Lowest rated',
                showFullReview: 'Show full review',
                hideFullReview: 'Show less',
                validatedReviewer: 'Validated Reviewer',
                singleStar: 'star',
                multiStars: 'stars',
                clearFilter: 'Clear filter',
                featuredReviews: 'Featured Reviews',
                customerReviews: 'Customer Reviews'
            }
        },
        fetchMore: () => {},
        setPaginatedData: () => {},
        refetch: () => {}
    },
    settings: {
        position: SETTINGS.position.defaultValue,
        moreReviewsLabel: SETTINGS.moreReviewsLabel.defaultValue,
        isAvatarVisible: true
    }
};

export default ProductReviews;
