import React, { useEffect, useReducer, useState } from 'react';
import { Alert, Button, getContinents } from '@theorchard/suite-components';
import { isEmpty, pick } from 'lodash-es';
import { useHistory, useParams } from 'react-router-dom';
import {
    useCreateContractTerm,
    useUpdateContractTerm,
} from 'src/apollo/mutations/contract-term';
import {
    useCreateContractTermConditions,
    useDeleteContractTermConditions,
    useUpdateContractTermConditions,
} from 'src/apollo/mutations/contract-term-condition';
import { useContract } from 'src/apollo/queries/contract';
import { useContractTerm } from 'src/apollo/queries/contract-term';
import { useStoreList } from 'src/apollo/queries/stores';
import { useTransactionTypeGroups } from 'src/apollo/queries/transaction-types';
import { useTransactionTypes } from 'src/apollo/queries/transaction-types';
import { CONTRACT_TERM_ERROR_MSG } from 'src/apollo/type-constants/contract';
import { LinkButton } from 'src/components/shared/link-button';
import {
    ADD_CONTRACT_TERM_CONDITION,
    RECEIVE_CONTINENTS,
    CONTRACT_TERM_TYPES,
    EMPTY_TERM_CONDITIONS,
    RECEIVE_CONTRACT_TERM,
    RECEIVE_STORES,
    RECEIVE_TRANSACTION_TYPE_GROUPS,
    RECEIVE_TRANSACTION_TYPES,
    SET_ATTACHMENTS_RELATIONS,
    SET_DEFAULT_CONDITION,
    SET_ERRORS,
} from 'src/constants';
import { ContractTermContext } from 'src/contexts/contract-term-context';
import {
    initialTermState,
    reducer,
} from 'src/reducers/contract-term/contract-term-reducer';
import { getContractDetail } from 'src/urls/frontend-royalties';
import { formatConditions, updateConditions } from 'src/utils/contract-terms';
import {
    areBaseTermLabelsRemoved,
    contractTermFormValidation,
} from 'src/utils/form-validations/contract-term-form-validation';
import { BaseTermsForm } from './base-terms-form';
import { ProductExceptionForm } from './product-exception-form';
import TrackExceptionForm from './track-exception-form';
import type { GetContractTermQuery } from 'src/apollo/queries/contract-term/__generated__/get-contract-term';
import type { GenericObject } from 'src/types/generic-object';
import { TransactionTypeGroupAdmin } from 'src/apollo/definitions/globalTypes';
import './styles.scss';

type AbacusContractTerm = NonNullable<
    GetContractTermQuery['abacusContractTerm']
>;
type ContractTermCondition = AbacusContractTerm['conditions'] extends
    | (infer T)[]
    | null
    ? T
    : null;

export const ContractTerms: React.FC = () => {
    const {
        contractId,
        contractTermId: termId,
        termType: contractTermType,
    } = useParams<{
        contractId: string;
        contractTermId: string;
        termType: string;
    }>();
    const mapTermType: GenericObject = {
        terms: CONTRACT_TERM_TYPES.LABEL,
        'product-exception': CONTRACT_TERM_TYPES.PRODUCT,
        'track-exception': CONTRACT_TERM_TYPES.TRACK,
    };
    const mapContractTermComponents: any = {
        terms: BaseTermsForm,
        'product-exception': ProductExceptionForm,
        'track-exception': TrackExceptionForm,
    };
    const [state, dispatch]: any = useReducer(reducer, {
        ...initialTermState,
        contractId,
        termType: mapTermType[contractTermType],
        isBaseTerm: contractTermType === 'terms',
    });
    const history = useHistory();
    const [isSavingBaseTerm, setIsSavingBaseTerm] = useState(false);

    const setErrors = (error: any) => dispatch({ type: SET_ERRORS, error });
    let contractData: any;
    let contractTermData: any;

    const addContractTerm = useCreateContractTerm(contractId);
    const updateContractTerm = useUpdateContractTerm();
    const addContractTermConditions = useCreateContractTermConditions(
        contractId,
        termId
    );
    const updateContractTermConditions = useUpdateContractTermConditions(
        contractId,
        termId
    );
    const deleteContractTermConditions = useDeleteContractTermConditions(
        contractId,
        termId
    );

    const { data: storesData } = useStoreList();
    const { data: transactionTypeData } = useTransactionTypes();
    const { data: transactionTypeGroupData } = useTransactionTypeGroups(
        TransactionTypeGroupAdmin.CONTRACT_ADMIN
    );

    if (termId) ({ data: contractTermData } = useContractTerm(termId));

    if (!termId) ({ data: contractData } = useContract(contractId));

    useEffect(() => {
        dispatch({
            type: RECEIVE_CONTINENTS,
            continents: getContinents(),
        });
    }, []);

    useEffect(() => {
        if (storesData)
            dispatch({
                type: RECEIVE_STORES,
                stores: storesData?.deliveryStoresV2,
            });
    }, [storesData]);

    useEffect(() => {
        if (transactionTypeData)
            dispatch({
                type: RECEIVE_TRANSACTION_TYPES,
                transactionTypes: transactionTypeData?.transactionTypes,
            });
    }, [transactionTypeData]);

    useEffect(() => {
        if (transactionTypeGroupData)
            dispatch({
                type: RECEIVE_TRANSACTION_TYPE_GROUPS,
                transactionTypeGroups:
                    transactionTypeGroupData?.transactionTypeGroups,
            });
    }, [transactionTypeGroupData]);

    useEffect(() => {
        if (
            contractTermData &&
            !isEmpty(contractTermData?.abacusContractTerm)
        ) {
            const {
                attachments,
                attachmentsRelations,
                conditions,
                contract,
                contractTermId,
                isBaseTerm,
                termType,
            } = contractTermData.abacusContractTerm;
            const contractTerm = {
                attachments,
                attachmentsRelations,
                contract,
                contractId,
                contractTermId,
                isBaseTerm,
                termType,
            };

            let existingLabelAttachments: string[] = [];
            let contractTermExceptionsIds: string[] = [];
            if (isBaseTerm && [CONTRACT_TERM_TYPES.LABEL].includes(termType))
                existingLabelAttachments = attachments;

            if (
                isBaseTerm &&
                [
                    CONTRACT_TERM_TYPES.PRODUCT,
                    CONTRACT_TERM_TYPES.TRACK,
                ].includes(termType)
            )
                existingLabelAttachments = attachmentsRelations?.labelIds;

            if (contract?.contractTerms)
                contractTermExceptionsIds = contract?.contractTerms
                    ?.map(
                        (term: AbacusContractTerm) =>
                            !term.isBaseTerm && term.contractTermId
                    )
                    .filter(Boolean);

            dispatch({
                type: RECEIVE_CONTRACT_TERM,
                contractTerm,
                existingLabelAttachments,
                contractTermExceptionsIds,
            });
            if (conditions.length > 0) {
                dispatch({ type: EMPTY_TERM_CONDITIONS });
                conditions.forEach((condition: ContractTermCondition) => {
                    const { countryCodes, storeIds, transactionTypeIds } =
                        state;
                    dispatch({
                        type: ADD_CONTRACT_TERM_CONDITION,
                        condition: {
                            ...condition,
                            conditions: formatConditions(
                                condition?.conditions,
                                countryCodes,
                                storeIds,
                                transactionTypeIds
                            ),
                        },
                    });
                });
            } else dispatch({ type: SET_DEFAULT_CONDITION });
        }
    }, [contractTermData, state.stores]);

    useEffect(() => {
        if (contractData && !isEmpty(contractData?.abacusContract)) {
            let labelAttachmentIds: string[] = [];
            const contractTerms = contractData?.abacusContract?.contractTerms;

            const labelTerm = contractTerms.filter(
                (term: AbacusContractTerm) =>
                    term.termType === CONTRACT_TERM_TYPES.LABEL
            );
            if (isEmpty(labelTerm)) {
                const baseTerm = contractTerms.filter(
                    (term: AbacusContractTerm) => term.isBaseTerm
                );
                labelAttachmentIds =
                    baseTerm[0]?.attachmentsRelations?.labelIds?.map(
                        (attachmentId: string) => attachmentId.toString()
                    );
            } else
                labelAttachmentIds = labelTerm[0].attachments.map(
                    (attachmentId: string) => attachmentId.toString()
                );

            dispatch({
                type: SET_ATTACHMENTS_RELATIONS,
                attachmentsRelations: { labelIds: labelAttachmentIds },
            });
        }
    }, [contractData]);

    const prepareAttachmentsRelations = (attachmentRelations: any) => {
        const pickList: string[] = [];
        if (!attachmentRelations) return null;
        ['labelIds', 'upcs'].forEach((key: string) =>
            attachmentRelations[key] ? pickList.push(key) : null
        );
        if (isEmpty(pickList)) return null;
        return pick(attachmentRelations, pickList);
    };

    const saveContractTerm = async () => {
        const {
            attachments,
            contractTermId,
            isBaseTerm,
            attachmentsRelations,
            termType,
        } = state;
        const updatedAttachmentRelations =
            prepareAttachmentsRelations(attachmentsRelations);
        const requestMethod = contractTermId
            ? updateContractTerm
            : addContractTerm;
        let variables: any = {
            attachments,
            contractId,
            contractTermId:
                requestMethod === updateContractTerm ? contractTermId : null,
            isBaseTerm,
            termType,
        };

        if (updatedAttachmentRelations)
            variables = {
                ...variables,
                attachmentsRelations: updatedAttachmentRelations,
            };

        return await requestMethod({ variables }).then((response: any) => {
            if (!response) return;
            return contractTermId
                ? response?.data?.abacusUpdateContractTerm?.contractTermId
                : response?.data?.abacusCreateContractTerm?.contractTermId;
        });
    };

    const saveContractTermConditions = (contractTermId: string) => {
        const { countryCodes, storeIds, transactionTypeIds } = state;
        const conditionsToUpdate = state.conditions
            .filter(
                (condition: ContractTermCondition) =>
                    condition?.contractTermConditionId
            )
            .map((condition: ContractTermCondition) => ({
                conditions: updateConditions(
                    condition?.conditions,
                    countryCodes,
                    storeIds,
                    transactionTypeIds
                ),
                priority: condition?.priority,
                termRate: condition?.termRate,
                contractTermConditionId: condition?.contractTermConditionId,
            }));

        const conditionsToAdd = state.conditions
            .filter(
                (condition: ContractTermCondition) =>
                    !condition?.contractTermConditionId
            )
            .map((condition: ContractTermCondition) => ({
                conditions: updateConditions(
                    condition?.conditions,
                    countryCodes,
                    storeIds,
                    transactionTypeIds
                ),
                priority: condition.priority,
                termRate: condition.termRate,
            }));

        const updateVariables = {
            conditionsPayload: conditionsToUpdate,
            contractTermId,
        };
        const addVariables = {
            conditionsPayload: conditionsToAdd,
            contractTermId,
        };

        if (!isEmpty(conditionsToUpdate))
            updateContractTermConditions({ variables: updateVariables })
                .then(() => history.push(getContractDetail(contractId)))
                .catch(error => setErrors(error));

        if (!isEmpty(conditionsToAdd))
            addContractTermConditions({ variables: addVariables })
                .then(() => history.push(getContractDetail(contractId)))
                .catch(error => setErrors(error));
    };

    const deleteTermConditions = async () => {
        const { deletedConditions } = state;
        const existingConditionsToDelete = deletedConditions.map(
            (condition: ContractTermCondition) =>
                condition.contractTermConditionId !== null &&
                condition.contractTermConditionId
        );
        if (!isEmpty(existingConditionsToDelete))
            return await deleteContractTermConditions({
                variables: {
                    contractTermConditionId: existingConditionsToDelete,
                },
            }).catch(error => setErrors(error));

        return await Promise.resolve();
    };

    const renderBaseTermLabelErrorMessage = () => {
        let errorMessage = '';
        const termType =
            state.termType.charAt(0).toUpperCase() + state.termType.slice(1);
        if ([CONTRACT_TERM_TYPES.LABEL].includes(state.termType))
            errorMessage =
                `${termType} has product and/or track exceptions on this contract. ` +
                'You cannot remove the label in use from the base term.';

        if (
            [CONTRACT_TERM_TYPES.PRODUCT, CONTRACT_TERM_TYPES.TRACK].includes(
                state.termType
            )
        )
            errorMessage =
                `${termType} has exceptions on this contract. ` +
                'You cannot remove the label in use from the base term.';

        setErrors([errorMessage]);
    };

    const saveHandler = () => {
        const validationErrors = contractTermFormValidation(state);
        if (validationErrors.length > 0) return setErrors(validationErrors);

        if (areBaseTermLabelsRemoved(termId, state)) {
            renderBaseTermLabelErrorMessage();
            return false;
        }

        if (state.errors.length === 0) {
            setIsSavingBaseTerm(true);
            saveContractTerm()
                .then(
                    async contractTermId =>
                        await deleteTermConditions().then(() =>
                            saveContractTermConditions(contractTermId)
                        )
                )
                .catch(error => {
                    setErrors(error);
                    setIsSavingBaseTerm(false);
                });
        }
    };
    const ContractTermComponent = mapContractTermComponents[contractTermType];

    return (
        <div className="ContractTermsForm">
            <div className="ContractTermsForm-error">
                {state.errors.length > 0 && (
                    <Alert
                        variant="error"
                        text={state.errors.map((error: any, index: number) => (
                            <div key={index}>
                                {error?.extensions?.response?.status === 500
                                    ? CONTRACT_TERM_ERROR_MSG
                                    : error}
                            </div>
                        ))}
                    />
                )}
                {state.attachmentsValidError.length > 0 && (
                    <Alert
                        variant="error"
                        text={state.attachmentsValidError.map((error: any) => (
                            <div key={error}>{error}</div>
                        ))}
                    />
                )}
            </div>
            <ContractTermContext.Provider value={{ state, dispatch }}>
                <ContractTermComponent />
            </ContractTermContext.Provider>

            <div className="ContractTermsForm-btn-wrap">
                <div className="ContractTermsForm-buttons">
                    <LinkButton
                        buttonSize="md"
                        label="Cancel"
                        target={getContractDetail(contractId)}
                        variant="default"
                    />
                    <Button
                        disabled={
                            !isEmpty(state.errors) ||
                            isSavingBaseTerm ||
                            !isEmpty(state.attachmentsValidError)
                        }
                        onClick={saveHandler}
                        variant="primary"
                    >
                        Save
                    </Button>
                </div>
            </div>
        </div>
    );
};

export default ContractTerms;
