import React, { useState, useEffect } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { formatMessage } from '@orchard/frontend-localization';
import { useQuery, useLazyQuery } from '@apollo/client';
import { ExecutionResult } from 'graphql';
import { Alert, Button, Form, Help, InfoIcon } from '@orchard/frontend-react-components';
import { Prompt, RouteComponentProps } from 'react-router-dom';
import { get, filter, find, isEmpty, omit, some } from 'lodash';
import ConfirmLeavePrompt from 'src/components/confirm-leave-prompt/confirm-leave-prompt';
import TableCellLoader from 'src/components/loaders/table-cell-loader';
import ConfirmModal from 'src/components/confirm-modal/confirm-modal';
import { RootState } from 'src/store/store';
import { useTransferWiseProfileQuery } from 'src/queries/tw-profile';
import { RecipientFormInput } from './recipient-form-input/recipient-form-input';
import {
    RECIPIENT_FIELD_MAP,
    CURRENCIES_WITH_ADDRESS,
    InfoBox,
    InfoBoxBody
} from './recipient-field-map';
import { COLLABORATOR_QUERY, } from './queries/collaborator';
import {
    GET_TW_ACCOUNT_REQUIREMENTS_QUERY,
    TransferWiseAccountRequirementsFields
} from './queries/tw-account-requirements';
import { GET_TW_AUTHORIZATION_URL_QUERY } from '../../queries/tw-authorization-url';
import { useVendorCurrencyQuery } from '../../queries/vendor-currency';
import {
    useCreateTransferWiseRecipientMutation
} from '../../mutations/create-transferwise-recipient';
import {
    useRefreshTransferWiseRecipientRequirementsMutation
} from '../../mutations/refresh-transferwise-recipient-requirements';
import {
    changePaymentDetailsFieldAction,
    discardPaymentDetailsAction
} from '../../actions/payment-details';
import {
    BACKEND_ERROR_CODES,
    PAYMENT_DETAILS_PRESETS,
    PAYMENT_DETAILS_FIELD_KEY_CURRENCY
} from '../../constants';
import { getCollaboratorUrl } from '../../urls/collaborator';
import getRecipientVariables from '../../utils/get-recipient-variables';
import { formatCollaboratorName } from '../../utils/formatters';
import messages from './i18n';

const CLASS_NAME = 'RecipientPage';

type Params = { collaboratorId: string };

const mapStateToProps = ({ paymentDetails }: RootState) => ({ paymentDetails });

const connector = connect(mapStateToProps);

type Props = ConnectedProps<typeof connector> & RouteComponentProps<Params>;

export const RecipientPage: React.FC<Props> = ({ match, history, paymentDetails, dispatch }) => {
    const [isModalOpen, setIsModalOpen] = useState(false);
    const [transferWiseConnectionError, setTransferWiseConnectionError] = useState(false);
    const [fieldErrors, setFieldErrors] = useState<{ [key: string]: string }>({});
    const [showApiError, setShowApiError] = useState(false);
    const [shouldRefreshRequirements, setShouldRefreshRequirements] = useState(false);

    const areChangesUnsaved = !isEmpty(omit(paymentDetails, PAYMENT_DETAILS_FIELD_KEY_CURRENCY));
    const collaboratorId = parseInt(match.params.collaboratorId, 10);

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

    const vendorCurrency = vendorCurrencyData!.vendorCurrency!.code!;

    const {
        data: { collaborator } = { collaborator: { } },
    } = useQuery(COLLABORATOR_QUERY, { variables: { collaboratorId } });


    const [getTransferRequirements, {
        data: { transferWiseAccountRequirements } = { transferWiseAccountRequirements: [] },
        loading: requirementsLoading
    }] = useLazyQuery(GET_TW_ACCOUNT_REQUIREMENTS_QUERY);

    useEffect(() => {
        // Default to currency of vendor
        if (vendorCurrency)
            dispatch(changePaymentDetailsFieldAction(PAYMENT_DETAILS_FIELD_KEY_CURRENCY, vendorCurrency));
    }, [vendorCurrency, dispatch]);

    useEffect(() => {
        // Update transfer requirements when currency changes
        if (paymentDetails.currency)
            getTransferRequirements({
                variables: {
                    source: vendorCurrency,
                    target: paymentDetails.currency,
                },
            });
    }, [paymentDetails.currency, vendorCurrency, getTransferRequirements]);

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

    const profileId = transferWiseProfile ? transferWiseProfile.profileId : null;
    const fieldMap = paymentDetails.currency ? RECIPIENT_FIELD_MAP[paymentDetails.currency] : null;
    const supportedType = fieldMap && fieldMap.supportedType;

    const {
        data: { transferWiseAuthorizationUrl } = {},
    } = useQuery(GET_TW_AUTHORIZATION_URL_QUERY);

    const currentAccountRequirements = find(
        transferWiseAccountRequirements, { type: supportedType }
    ) || transferWiseAccountRequirements[0];

    let paymentFields: TransferWiseAccountRequirementsFields[] = [];

    if (currentAccountRequirements)
        paymentFields = [
            // Prepend any preset fields where not present in account requirements
            ...PAYMENT_DETAILS_PRESETS.filter(({ key }) => !some(currentAccountRequirements.fields, { key })),
            ...currentAccountRequirements.fields
        ];

    const recipientVariables = profileId !== null && getRecipientVariables(
        profileId,
        collaboratorId,
        currentAccountRequirements && currentAccountRequirements.type,
        vendorCurrency,
        paymentDetails
    );

    const [createRecipientMutation, { loading: mutationLoading }] = useCreateTransferWiseRecipientMutation();

    const [refreshAccountRequirementsMutation] = useRefreshTransferWiseRecipientRequirementsMutation(
        vendorCurrency,
        paymentDetails.currency || ''
    );

    useEffect(() => {
        // Refresh needs to be called in this hook to prevent Redux race condition
        if (shouldRefreshRequirements && recipientVariables) {
            refreshAccountRequirementsMutation({
                variables: recipientVariables,
            });
            setShouldRefreshRequirements(false);
        }
    }, [
        shouldRefreshRequirements,
        refreshAccountRequirementsMutation,
        recipientVariables,
    ]);

    const getFromErrorBody = (error: string, field: string) =>
        get(
            get(error, 'graphQLErrors[0]') || error,
            `extensions.response.body.${field}`
        );

    const isInvalidPostError = (response: ExecutionResult) => {
        const [responseError] = get(response, 'errors') || get(response, 'graphQLErrors') || [];
        return getFromErrorBody(responseError, 'code') === BACKEND_ERROR_CODES.TRANSFERWISE_POST_ERROR;
    };

    const isOauthError = (response: ExecutionResult) => {
        const [responseError] = get(response, 'errors') || get(response, 'graphQLErrors') || [];
        return getFromErrorBody(responseError, 'code') === 'oauth_error';
    };

    const getErrorsFromResponse = (response: ExecutionResult) => {
        const [responseError] = get(response, 'errors') || get(response, 'graphQLErrors') || [];
        return get(JSON.parse(getFromErrorBody(responseError, 'message')), 'errors') || [];
    };

    const setErrorsFromResponse = (responseErrors: ExecutionResult['errors']) => {
        if (responseErrors)
            responseErrors.forEach(error => {
                if (error.path && error.message && typeof error.path === 'string')
                    setFieldErrors({ ...fieldErrors, [error.path]: error.message });
            });
    };

    const validatePaymentDetails = () => {
        const errors: { [key: string]: string } = {};
        paymentFields.forEach((req) => {
            const field = paymentDetails[req.key];
            // Check for required
            if (!field && req.required)
                errors[req.key] = formatMessage(messages.blankField);
            // Check for min lengths
            if (field && req.minLength && (field.length < req.minLength))
                errors[req.key] = formatMessage(messages.minLength, { length: req.minLength });
            if (field && req.maxLength && (field.length > req.maxLength))
                errors[req.key] = formatMessage(messages.maxLength, { length: req.maxLength });
            if (field && req.maxLength && req.minLength && (req.maxLength === req.minLength)
                && (field.length !== req.maxLength))
                errors[req.key] = formatMessage(messages.exactLength, { length: req.maxLength });
            // Check for invalid format
            if (field && req.validationRegexp) {
                const regex = new RegExp(req.validationRegexp);
                if (!regex.test(field))
                    errors[req.key] = formatMessage(messages.invalidFormat);
            }
        });
        setFieldErrors(errors);
        return errors;
    };

    const handleBack = (pushState = {}) => {
        dispatch(discardPaymentDetailsAction());
        return history.push(getCollaboratorUrl(collaboratorId), { ...pushState });
    };

    const handleOnSave = async () => {
        if (!recipientVariables) return;

        setShowApiError(false);
        let result;
        try {
            result = await createRecipientMutation({
                variables: recipientVariables,
            });
        } catch {
            return setShowApiError(true);
        }

        if (!result?.data?.createTransferWiseRecipient)
            if (isInvalidPostError(result)) {
                const responseErrors: ExecutionResult['errors'] = getErrorsFromResponse(result);

                const hasNonFieldErrors = responseErrors && responseErrors.some(({ path }) => !path);

                if (!hasNonFieldErrors) {
                    setErrorsFromResponse(responseErrors);
                    return setIsModalOpen(false);
                }

                return setShowApiError(true);
            } else if (isOauthError(result))
                return setTransferWiseConnectionError(true);
            else
                return setShowApiError(true);

        return handleBack({ recipientCreated: true });
    };

    const handleOnSaveClick = async () => {
        const errors = validatePaymentDetails();
        if (isEmpty(errors)) {
            setShowApiError(false);
            setIsModalOpen(true);
        }
    };

    const handleFieldChange = (field: string) => async (value: string, refreshRequirements: boolean) => {
        await dispatch(changePaymentDetailsFieldAction(field, value));
        // Clear any errors for this field if present
        setFieldErrors(omit(fieldErrors, field));
        if (refreshRequirements)
            setShouldRefreshRequirements(true);
    };

    /* eslint-disable react/no-array-index-key */
    const renderInfoBox = (infoBox: InfoBox) => (
        <div className={ `${CLASS_NAME}-section-info-box` } key={ infoBox.header }>
            <h4 className={ `${CLASS_NAME}-section-info-box-header` }>
                <InfoIcon className={ `${CLASS_NAME}-info-icon` } />
                { formatMessage(infoBox.header) }
            </h4>
            { infoBox.body.map((bodyPart: InfoBoxBody, index: number) => {
                if (bodyPart.link) {
                    const messageParts = formatMessage(
                        bodyPart.text
                    ).split(bodyPart.link.separator);

                    return (
                        <div className={ `${CLASS_NAME}-section-info-box-body-part` } key={ index }>
                            { messageParts[0] }
                            <a href={ bodyPart.link.getUrl() } target="_blank" rel="noopener noreferrer">
                                { formatMessage(bodyPart.link.text) }
                            </a>
                            { messageParts[1] || '' }
                        </div>
                    );
                }
                return (
                    <div className={ `${CLASS_NAME}-section-info-box-body-part` } key={ index }>
                        { formatMessage(bodyPart.text) }
                    </div>
                );
            }) }
        </div>
    );
    /* eslint-enable */
    const renderFormFields = () => {
        if (!paymentDetails.currency || requirementsLoading)
            /* eslint-disable react/no-array-index-key */
            return new Array(10).fill(null).map((_, index) => (
                <div key={ index } className={ `${CLASS_NAME}-loader` }>
                    <div className={ `${CLASS_NAME}-loader-label` }>
                        <TableCellLoader variant="extra-thick" />
                    </div>
                    <div className={ `${CLASS_NAME}-loader-input` }>
                        <TableCellLoader variant="extra-thick" />
                    </div>
                </div>
            ));
            /* eslint-enable */
        // For now we know that we only support one currency and payment type
        // In the future though this should be updated with a fallback if there is no map
        if (!fieldMap && paymentDetails.currency && paymentFields.length)
            return paymentFields.map((field) => (
                <RecipientFormInput
                    key={ field.key }
                    inputKey={ field.key }
                    name={ field.name }
                    type={ field.type }
                    isRequired={ Boolean(field.required) }
                    valuesAllowed={ field.valuesAllowed }
                    example={ field.example }
                    refreshRequirementsOnChange={ Boolean(field.refreshRequirementsOnChange) }
                    onHandleChange={ handleFieldChange(field.key) }
                    value={ paymentDetails[field.key] }
                    error={ fieldErrors[field.key] }
                />
            ));

        const shouldRenderAddress = CURRENCIES_WITH_ADDRESS.source.includes(vendorCurrency)
            || CURRENCIES_WITH_ADDRESS.target.includes(paymentDetails.currency);

        let displayFields = fieldMap && fieldMap.fieldMaps && supportedType
            ? [...fieldMap.fieldMaps[supportedType]]
            : [];

        if (!shouldRenderAddress && displayFields.length)
            displayFields = filter(displayFields, (field) =>
                field.key !== 'recipient-address');

        const mappedFieldKeys: string[] = [];

        displayFields.forEach(({ fields }) => { mappedFieldKeys.push(...fields); });

        const unmappedFieldKeys = paymentFields
            .filter(({ key }) => !mappedFieldKeys.includes(key))
            .map(({ key }) => key);

        if (unmappedFieldKeys.length)
            displayFields.push({
                title: messages.other,
                key: 'other',
                fields: unmappedFieldKeys
            });

        return displayFields.map(({ fields, title, key, tooltip, infoBox, fieldTooltips }) => {
            const section: JSX.Element[] = [];
            fields.forEach((field: string) => {
                const mappedField = find(paymentFields, { key: field });
                if (mappedField)
                    section.push(
                        (<RecipientFormInput
                            key={ mappedField.key }
                            inputKey={ mappedField.key }
                            name={ mappedField.name }
                            type={ mappedField.type }
                            isRequired={ Boolean(mappedField.required) }
                            valuesAllowed={ mappedField.valuesAllowed }
                            example={ mappedField.example }
                            refreshRequirementsOnChange={ Boolean(mappedField.refreshRequirementsOnChange) }
                            onHandleChange={ handleFieldChange(mappedField.key) }
                            value={ paymentDetails[mappedField.key] }
                            error={ fieldErrors[mappedField.key] }
                            maxLength={ mappedField.maxLength }
                            tooltip={ fieldTooltips && fieldTooltips[field] }
                        />)
                    );
            });
            return (
                <div className={ `${CLASS_NAME}-section` } key={ key }>
                    <h2 className={ `${CLASS_NAME}-section-header` }>
                        { formatMessage(title) }
                        { tooltip && (
                            <Help
                                id={ `${key}-section-tooltip` }
                                message={ formatMessage(tooltip) }
                            />
                        ) }
                    </h2>
                    <div className={ `${CLASS_NAME}-section-body` }>
                        <div className={ `${CLASS_NAME}-section-fields` }>
                            { section }
                        </div>
                        { infoBox && renderInfoBox(infoBox) }
                    </div>
                </div>
            );
        });
    };

    const renderHeaderMessage = () => {
        const headerMessageParts = formatMessage(
            messages.createNewRecipient
        ).split('{name}');

        return (
            <h1>
                { headerMessageParts[0] }
                <strong>{ formatCollaboratorName(collaborator.name, collaborator.collaboratorType) }</strong>
            </h1>
        );
    };

    const renderSaveModal = () => {
        const messageParts = formatMessage(
            messages.wiseAccessInvalid
        ).split('{here}');

        const body = (
            <div>
                { transferWiseConnectionError && (
                    <Alert dismissible={ false } variant="danger">
                        { messageParts[0] }
                        <a href={ transferWiseAuthorizationUrl.url }>
                            { formatMessage(messages.here) }
                        </a>
                        { messageParts[1] || '' }
                    </Alert>
                ) }
                <span>{ formatMessage(messages.wisePaymentInfoMessage) }</span>
            </div>
        );

        return (
            <ConfirmModal
                contentClass={ `${CLASS_NAME}-modal-content` }
                onConfirm={ handleOnSave }
                onCancel={ () => setIsModalOpen(false) }
                header={ formatMessage(messages.createNewRecipientQuestion) }
                body={ body }
                confirmLabel={ formatMessage(messages.yesCreate) }
                cancelLabel={ formatMessage(messages.noCancel) }
                isLoading={ mutationLoading }
                isError={ showApiError }
                saveButtonVariant="loading"
            />
        );
    };

    return (
        <div className={ CLASS_NAME }>
            <ConfirmLeavePrompt show={ areChangesUnsaved } />
            <Prompt
                when={ areChangesUnsaved }
                message={ formatMessage(messages.changesNotSaved) }
            />
            { isModalOpen && renderSaveModal() }
            <div className={ `${CLASS_NAME}-container` }>
                <Form className={ `${CLASS_NAME}-form` }>
                    <div className={ `${CLASS_NAME}-header` }>
                        { renderHeaderMessage() }
                        <div className={ `${CLASS_NAME}-legend` }>
                            { `* ${formatMessage(messages.required)}` }
                        </div>
                    </div>
                    <div className={ `${CLASS_NAME}-options` }>
                        { !vendorCurrencyLoading && renderFormFields() }
                    </div>
                    { paymentDetails.currency && !requirementsLoading && currentAccountRequirements && (
                        <div className={ `${CLASS_NAME}-footer-buttons` }>
                            <Button
                                className={ `${CLASS_NAME}-footer-button-cancel` }
                                type="button"
                                onClick={ () => handleBack() }
                                aria-label={ formatMessage(messages.cancel) }
                                data-testid="payment-edit-cancel-button"
                            >
                                { formatMessage(messages.cancel) }
                            </Button>
                            <Button
                                className={ `${CLASS_NAME}-footer-button-save` }
                                type="button"
                                variant="primary"
                                onClick={ handleOnSaveClick }
                                aria-label={ formatMessage(messages.save) }
                                data-testid="payment-edit-save-button"
                            >
                                { formatMessage(messages.save) }
                            </Button>
                        </div>
                    ) }
                </Form>
            </div>
        </div>
    );
};

export default connect(mapStateToProps)(RecipientPage);
