import React, { useEffect, useRef, useState } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { Prompt, RouteComponentProps } from 'react-router-dom';
import { find, forEach, some } from 'lodash';
import ConfirmLeavePrompt from 'src/components/confirm-leave-prompt/confirm-leave-prompt';
import BackNav from 'src/components/back-nav/back-nav';
import {
    Button,
    Form,
    ToastContainer,
    WarningGlyph
} from '@orchard/frontend-react-components';
import { formatMessage } from '@orchard/frontend-localization';
import ConfirmModal from 'src/components/confirm-modal/confirm-modal';
import TableCellLoader from 'src/components/loaders/table-cell-loader';
import { useTransferWiseProfileQuery } from 'src/queries/tw-profile';
import { RootState } from 'src/store/store';
import { usePaymentPageCollaboratorsQuery, Collaborator } from './queries/collaborators';
import { useVendorCurrencyQuery } from '../../queries/vendor-currency';
import QuotePanelConnected from './quote-panel/quote-panel';
import {
    changeCollaboratorPaymentValueAction,
    discardPaymentValuesAction,
    discardCollaboratorPaymentValue,
    discardQuoteResponse
} from '../../actions/payments';
import messages from './i18n';
import CollaboratorRow from './collaborator-row';

const CLASS_NAME = 'PaymentsPage';

const connector = connect(({
    payments: { paymentList, quotes }
}: RootState) => ({ paymentList, quotes }));
type PropsFromRedux = ConnectedProps<typeof connector>;

type Props = RouteComponentProps & PropsFromRedux;

export const PaymentsPage: React.FC<Props> = ({ paymentList, quotes, dispatch, history }) => {
    useEffect(() => () => { dispatch(discardPaymentValuesAction()); }, [dispatch]);

    const [selectedCollaborators, setSelectedCollaborators] = useState<{ [collaboratorId: string]: Collaborator }>({});
    const [ignoredCollaborators, setIgnoredCollaborators] = useState(0);
    const [showReplaceBalanceModal, setShowReplaceBalanceModal] = useState(false);

    // Ref used to optionally set an indeterminate state for the select all toggle
    const selectAllRef = useRef<HTMLInputElement>(null);

    const {
        data: collaboratorsData,
        loading: collaboratorsLoading,
    } = usePaymentPageCollaboratorsQuery({ hasRecipient: true });

    const {
        data: { transferWiseProfile } = {},
    } = useTransferWiseProfileQuery();

    const {
        loading: vendorCurrencyLoading,
        data: vendorCurrencyData,
    } = useVendorCurrencyQuery();

    const vendorCurrency = vendorCurrencyData.vendorCurrency?.code;

    const profile = transferWiseProfile ? transferWiseProfile.profileId : null;
    const collaborators = collaboratorsData?.collaborators.collaborators || [];
    const validCollaboratorRowCount = collaborators.filter(({ currency }) => currency === vendorCurrency).length;

    const getPaymentAmountChangeHandler = (
        collaboratorId: number,
        recipientTransferWiseId: number,
        targetCurrency: string,
    ) => (value: number | null) => {
        if (!profile) return;

        if (quotes.length) dispatch(discardQuoteResponse());

        dispatch(
            changeCollaboratorPaymentValueAction({
                collaboratorId,
                profile,
                recipientTransferWiseId,
                sourceCurrency: vendorCurrency!,
                targetCurrency,
                sourceAmount: value?.toString(),
            })
        );
    };

    const handleCollaboratorSelect = (collaborator: Collaborator) => () => {
        if (!selectAllRef.current) return;

        const { [collaborator.id.toString()]: exists, ...remainingCollaborators } = selectedCollaborators;
        let updatedCollaborators = { ...remainingCollaborators };
        // If collaborator is already selected, just use the remainder set to delete it
        // Otherwise, cue up a payment value object for use with
        // the changeCollaboratorPaymentValueAction
        if (!exists)
            updatedCollaborators = { ...remainingCollaborators, [collaborator.id]: collaborator };

        // Manually toggling a collaborator sets the indeterminate state if not all selected
        const numSelected = Object.keys(updatedCollaborators).length;
        if (numSelected && numSelected !== collaborators.length)
            selectAllRef.current.indeterminate = true;
        else
            selectAllRef.current.indeterminate = false;

        return setSelectedCollaborators(updatedCollaborators);
    };

    const handleClearAmount = () => {
        if (!selectAllRef.current) return;

        selectAllRef.current.indeterminate = false;
        if (quotes.length)
            dispatch(discardQuoteResponse());
        forEach(selectedCollaborators, ({ id }) =>
            dispatch(discardCollaboratorPaymentValue(id)));
        setSelectedCollaborators({});
    };

    const fillFromBalance = () => {
        if (!selectAllRef.current) return;

        selectAllRef.current.indeterminate = false;
        if (quotes.length)
            dispatch(discardQuoteResponse());
        let skipped = 0;
        forEach(selectedCollaborators, (collaborator) => {
            const updatedCollaborator = {
                collaboratorId: collaborator.id,
                profile: profile!,
                recipientTransferWiseId: collaborator.recipient!.transferWiseId,
                sourceCurrency: vendorCurrency!,
                targetCurrency: collaborator.recipient!.currency,
                sourceAmount: collaborator.balance.toString(),
            };
            if (collaborator.balance > 0)
                dispatch(changeCollaboratorPaymentValueAction(updatedCollaborator));
            else
                skipped++;
        });
        setIgnoredCollaborators(skipped);
        setShowReplaceBalanceModal(false);
        setSelectedCollaborators({});
    };

    const handleFillFromBalance = () => {
        const selectedCollaboratorIds = new Set(Object.keys(selectedCollaborators));
        if (Object.keys(paymentList).some(collaboratorId => selectedCollaboratorIds.has(collaboratorId)))
            return setShowReplaceBalanceModal(true);
        fillFromBalance();
    };

    const handleToggleAll = () => {
        if (!selectAllRef.current) return;

        // If all were already selected, deselect all
        const numSelected = Object.keys(selectedCollaborators).length;
        if (numSelected === validCollaboratorRowCount)
            return setSelectedCollaborators({});

        const updatedCollaborators: { [collaboratorId: string]: Collaborator } = {};

        collaborators.forEach((collaborator) => {
            if (collaborator.currency === vendorCurrency)
                updatedCollaborators[collaborator.id] = collaborator;
        });

        selectAllRef.current.indeterminate = Object.keys(updatedCollaborators).length !== validCollaboratorRowCount;
        return setSelectedCollaborators(updatedCollaborators);
    };

    const renderBalanceWarning = () => {
        if (!some(selectedCollaborators, ({ balance }) => (balance <= 0)))
            return null;
        return (
            <div className={ `${CLASS_NAME}-balance-warning` }>
                <WarningGlyph />
                { formatMessage(messages.invalidBalanceWarning) }
            </div>
        );
    };

    const renderTableRows = () => {
        if (collaboratorsLoading || vendorCurrencyLoading)
            /* eslint-disable react/no-array-index-key */
            return new Array(10).fill(null).map((_, index) => (
                <div key={ index } className={ `${CLASS_NAME}-collaborator-row` }>
                    <div className={ `${CLASS_NAME}-column ${CLASS_NAME}-checkmark-column` } />
                    <div className={ `${CLASS_NAME}-column ${CLASS_NAME}-collaborator-row-data ${CLASS_NAME}-collaborator-column` }>
                        <TableCellLoader />
                    </div>
                    <div className={ `${CLASS_NAME}-column ${CLASS_NAME}-collaborator-row-data ${CLASS_NAME}-balance-column` }>
                        <TableCellLoader />
                    </div>
                    <div
                        className={
                            `${CLASS_NAME}-column ${CLASS_NAME}-collaborator-row-data ${CLASS_NAME}-amount-column`
                        }
                    >
                        <TableCellLoader />
                    </div>
                </div>
            ));
            /* eslint-enable */
        return collaborators.map((collaborator) => (
            <CollaboratorRow
                key={ collaborator.id }
                collaborator={ collaborator }
                amount={ paymentList[collaborator.id]?.sourceAmount }
                isAmountInvalid={ paymentList[collaborator.id]?.invalidAmount }
                onAmountChange={ getPaymentAmountChangeHandler(
                    collaborator.id,
                    collaborator.recipient!.transferWiseId,
                    collaborator.recipient!.currency
                ) }
                quote={ find(quotes, ['collaboratorId', collaborator.id]) }
                isSelected={ Boolean(selectedCollaborators[collaborator.id]) }
                onSelectedToggle={ handleCollaboratorSelect(collaborator) }
                vendorCurrency={ vendorCurrency! }
                hasCurrencyMismatch={ collaborator.currency !== vendorCurrency }
            />
        ));
    };

    const numCollaborators = Object.keys(paymentList).length;
    const areChangesUnsaved = numCollaborators !== 0;
    const numCollaboratorsSelected = Object.keys(selectedCollaborators).length;
    const balanceButtonSuffix = numCollaboratorsSelected
        ? `(${numCollaboratorsSelected})` : '';

    const renderHeaders = () => (
        <>
            <div className={ `${CLASS_NAME}-column ${CLASS_NAME}-collaborator-name-column` }>
                { formatMessage(messages.collaborators) }
            </div>
            <div className={ `${CLASS_NAME}-column ${CLASS_NAME}-original-balance-column` }>
                { formatMessage(messages.balance) }
            </div>
            <div className={ `${CLASS_NAME}-column ${CLASS_NAME}-source-column` }>
                { formatMessage(messages.youSend) }
            </div>
            <div className={ `${CLASS_NAME}-column ${CLASS_NAME}-target-column` }>
                { formatMessage(messages.collaboratorGets) }
            </div>
            <div className={ `${CLASS_NAME}-column ${CLASS_NAME}-remaining-balance-column` }>
                { formatMessage(messages.remainingBalance) }
            </div>
        </>
    );

    return (
        <div className={ CLASS_NAME }>
            <ConfirmLeavePrompt show={ areChangesUnsaved } />
            <Prompt
                when={ areChangesUnsaved }
                message={ formatMessage(messages.changesNotSaved) }
            />
            { Boolean(ignoredCollaborators) && (
                <ToastContainer
                    messages={ [
                        formatMessage(messages.balanceNotFilled, { amount: ignoredCollaborators })
                    ] }
                    setMessages={ () => setIgnoredCollaborators(0) }
                    toastTime={ 3000 }
                />
            ) }
            <div className={ `${CLASS_NAME}-collaborator-section` }>
                <BackNav />
                <h2>{ formatMessage(messages.payments) }</h2>
                <div className={ `${CLASS_NAME}-balance-buttons` }>
                    <Button
                        className={ `${CLASS_NAME}-balance-button-clear` }
                        type="button"
                        size="sm"
                        onClick={ handleClearAmount }
                        aria-label={ formatMessage(messages.clearAmount) }
                        disabled={ !numCollaboratorsSelected }
                        data-testid="balance-clear-button"
                    >
                        { `${formatMessage(messages.clearAmount)} ${balanceButtonSuffix}` }
                    </Button>
                    <Button
                        className={ `${CLASS_NAME}-balance-button-fill` }
                        type="button"
                        size="sm"
                        onClick={ handleFillFromBalance }
                        aria-label={ formatMessage(messages.fillFromBalance) }
                        disabled={ !numCollaboratorsSelected }
                        data-testid="balance-fill-button"
                    >
                        { `${formatMessage(messages.fillFromBalance)} ${balanceButtonSuffix}` }
                    </Button>
                    { renderBalanceWarning() }
                </div>
                <div className={ `${CLASS_NAME}-table` }>
                    <div className={ `${CLASS_NAME}-table-header` }>
                        <div className={ `${CLASS_NAME}-column ${CLASS_NAME}-checkmark-column` }>
                            <Form.Check
                                id="select-all-collaborator-checkbox"
                                data-testid="select-all-collaborator-checkbox"
                                type="checkbox"
                                onChange={ handleToggleAll }
                                checked={
                                    Boolean(numCollaboratorsSelected) && (numCollaboratorsSelected === validCollaboratorRowCount)
                                }
                                ref={ selectAllRef }
                            />
                        </div>
                        { renderHeaders() }
                    </div>
                    <div className={ `${CLASS_NAME}-table-body` }>
                        { renderTableRows() }
                    </div>
                </div>
            </div>
            { profile && <QuotePanelConnected profileId={ profile } history={ history } /> }
            { showReplaceBalanceModal && (
                <ConfirmModal
                    onCancel={ () => setShowReplaceBalanceModal(false) }
                    onConfirm={ fillFromBalance }
                    header={ formatMessage(messages.replaceAmounts) }
                    body={ formatMessage(messages.balanceReplaceWarning) }
                    confirmLabel={ formatMessage(messages.yesReplace) }
                    cancelLabel={ formatMessage(messages.noCancel) }
                />
            ) }
        </div>
    );
};

export default connector(PaymentsPage);
