import React, { useEffect, useReducer, useState } from 'react';
import {
    Alert,
    LoadingIndicator,
    Button,
    getContinents,
} from '@theorchard/suite-components';
import { isEmpty } from 'lodash-es';
import { useHistory, useParams } from 'react-router-dom';
import {
    AbacusContractType,
    TransactionTypeGroupAdmin,
} from 'src/apollo/definitions/globalTypes';
import {
    useCreateContractTerm,
    useSoftDeleteContractTerm,
    useUpdateContractTerm,
} from 'src/apollo/mutations/contract-term';
import {
    useCreateContractTermConditions,
    useDeleteContractTermConditions,
    useUpdateContractTermConditions,
} from 'src/apollo/mutations/contract-term-condition';
import { useContractById } from 'src/apollo/queries/contract';
import { useContractTerm } from 'src/apollo/queries/contract-term';
import { useStoreList } from 'src/apollo/queries/stores';
import { useTransactionTypes } from 'src/apollo/queries/transaction-types';
import { useTransactionTypeGroups } from 'src/apollo/queries/transaction-types';
import {
    noContractTermFound,
    noContributorTerms,
    CONTRACT_TERM_ERROR_MSG,
} from 'src/apollo/type-constants/contract';
import { CopyTermConditionsFormPopup } from 'src/components/contract-terms-form-nr/copy-term-conditions-form-popup';
import { DeleteContractTermPopup } from 'src/components/contract-terms-form-nr/delete-contract-term-popup';
import { NContractTermsConditionsList } from 'src/components/contract-terms-form-nr/nr-contract-terms-conditions-list';
import { NrContractTermsFormFields } from 'src/components/contract-terms-form-nr/nr-contract-terms-form-fields';
import { LinkButton } from 'src/components/shared/link-button';
import {
    ADD_CONTRACT_TERM_CONDITION,
    CONTRACT_TYPES,
    CONTRACT_TERM_TYPES,
    EMPTY_TERM_CONDITIONS,
    RECEIVE_CONTINENTS,
    RECEIVE_CONTRACT_TERM,
    RECEIVE_STORES,
    RECEIVE_TRANSACTION_TYPE_GROUPS,
    RECEIVE_TRANSACTION_TYPES,
    SET_CONTRACT_TERM,
    SET_ERRORS,
    SET_DEFAULT_CONDITION,
} 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 { nrContractTermFormValidation } from 'src/utils/form-validations/contract-term-form-validation';
import type { GetContractTermQuery } from 'src/apollo/queries/contract-term/__generated__/get-contract-term';
import type { AbacusNrContractTermCondition } from 'src/types/abacus-contract-term';
import './styles.scss';

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

export const NrContractTermsForm: React.FC = () => {
    const { contractId, contractTermId: termId } = useParams<{
        contractId: string;
        contractTermId: string;
    }>();
    const history = useHistory();

    const [isSavingNrTerm, setIsSavingNrTerm] = useState<boolean>(false);
    const [isCopyConditionsModalOpen, setIsCopyConditionsModalOpen] =
        useState<boolean>(false);
    const [isDeleteContractTermModalOpen, setIsDeleteContractTermModalOpen] =
        useState<boolean>(false);

    const [state, dispatch]: any = useReducer(reducer, initialTermState);

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

    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]);

    let contract: any;
    let contractError;
    let contractTerm: any;
    let contractTermError;
    let loading;

    if (!termId)
        ({
            data: contract,
            error: contractError,
            loading,
        } = useContractById(contractId));

    if (termId)
        ({
            data: contractTerm,
            error: contractTermError,
            loading,
        } = useContractTerm(termId));

    const contractType =
        contract?.abacusContract?.contractType ||
        contractTerm?.abacusContractTerm?.contract?.contractType ||
        CONTRACT_TYPES.NEIGHBOURING_RIGHTS;

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

    const getScheduleIds = (
        termType: string,
        contractTermSchedules: AbacusContractTermSchedule[] | null
    ) => {
        let scheduleIds: string[] = [];
        contractTermSchedules?.forEach((contractTermSchedule: any) => {
            if (termType === CONTRACT_TERM_TYPES.CONTRIBUTOR_SCHEDULE) {
                const scheduleId = contractTermSchedule?.abacusSchedules?.map(
                    (schedule: any) => schedule.scheduleId
                );
                scheduleIds = [...scheduleIds, ...scheduleId];
            }

            if (termType === CONTRACT_TERM_TYPES.CONTRIBUTION_SCHEDULE)
                scheduleIds = [...scheduleIds, contractTermSchedule.scheduleId];
        });

        return scheduleIds;
    };

    useEffect(() => {
        if (
            contractTerm &&
            !isEmpty(contractTerm?.abacusContractTerm) &&
            !state.contractTermId &&
            contractType === CONTRACT_TYPES.NEIGHBOURING_RIGHTS &&
            !isEmpty(state.stores)
        ) {
            const {
                contractTermName,
                contractTermId,
                contract,
                termType,
                conditions,
                contractTermSchedules,
            }: any = contractTerm.abacusContractTerm;

            const scheduleIds = getScheduleIds(termType, contractTermSchedules);

            const contractTermData = {
                contractTermId,
                contractTermName,
                contract,
                contractId,
                termType,
                scheduleIds,
            };

            dispatch({
                type: RECEIVE_CONTRACT_TERM,
                contractTerm: contractTermData,
            });

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

    useEffect(() => {
        if (contract && !isEmpty(contract?.abacusContract) && !state.contract) {
            const { contractId, contractName, contractType }: any =
                contract.abacusContract;

            dispatch({
                type: SET_CONTRACT_TERM,
                name: 'contract',
                value: {
                    contractId,
                    contractName,
                    contractType,
                },
            });
        }
    }, [contract]);

    const saveContractTerm = async () => {
        const {
            contractTermId,
            contractTermName,
            termType,
            scheduleIds,
            isTermTypeChanged,
        } = state;
        const requestMethod =
            contractTermId && !isTermTypeChanged
                ? updateContractTerm
                : addContractTerm;
        const variables: any = {
            contractTermName,
            contractId,
            contractTermId:
                requestMethod === updateContractTerm ? contractTermId : null,
            scheduleIds,
            termType,
        };

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

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

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

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

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

        const queries = [];
        if (!isEmpty(conditionsToUpdate))
            queries.push(
                updateContractTermConditions({ variables: updateVariables })
            );

        if (!isEmpty(conditionsToAdd))
            queries.push(
                addContractTermConditions({ variables: addVariables })
            );

        return queries;
    };

    const deleteTermOrTermConditions = async () => {
        const { contractTermId, deletedConditions, isTermTypeChanged } = state;
        const existingConditionsToDelete: string[] = [];

        if (contractTermId && isTermTypeChanged)
            return await deleteContractTerm({ variables: { contractTermId } });
        else {
            deletedConditions.forEach(
                (condition: AbacusNrContractTermCondition) => {
                    if (condition.contractTermConditionId !== null)
                        existingConditionsToDelete.push(
                            condition.contractTermConditionId
                        );
                }
            );
            if (!isEmpty(existingConditionsToDelete))
                return await deleteContractTermConditions({
                    variables: {
                        contractTermConditionId: existingConditionsToDelete,
                    },
                });
        }

        return await Promise.resolve();
    };

    const saveHandler = () => {
        dispatch({ type: SET_ERRORS, error: [] });
        setIsSavingNrTerm(true);
        const validationErrors = nrContractTermFormValidation(state);
        if (validationErrors.length > 0) {
            setIsSavingNrTerm(false);
            dispatch({ type: SET_ERRORS, error: validationErrors });
            return;
        }

        saveContractTerm()
            .then(
                async (contractTermId: string) =>
                    await Promise.all(
                        saveContractTermConditions(contractTermId)
                    ).then(
                        async () =>
                            await deleteTermOrTermConditions().then(() =>
                                history.push(getContractDetail(contractId))
                            )
                    )
            )
            .catch(error => {
                dispatch({ type: SET_ERRORS, error: [error] });
                setIsSavingNrTerm(false);
            });
    };

    if (termId && !contractTerm?.abacusContractTerm && !loading)
        return (
            <div className="NrContractTermsForm-invalid-contract-term">
                <i>{noContractTermFound(termId)}</i>
            </div>
        );

    if (contractType !== CONTRACT_TYPES.NEIGHBOURING_RIGHTS && !loading)
        return (
            <div className="NrContractTermsForm-invalid-contract">
                <i>{noContributorTerms(contractType)}</i>
            </div>
        );

    if (loading) return <LoadingIndicator />;

    return (
        <div className="NrContractTermsForm">
            <div className="NrContractTermsForm-top-header">
                {<h2>{!termId ? 'Add Term' : 'Edit Term'}</h2>}
                {termId && (
                    <Button
                        className="delete-button"
                        disabled={isSavingNrTerm}
                        onClick={() => setIsDeleteContractTermModalOpen(true)}
                        variant="danger"
                    >
                        Delete
                    </Button>
                )}
            </div>

            {(contractError || contractTermError) && (
                <Alert
                    variant="error"
                    text={contractError?.message || contractTermError?.message}
                />
            )}
            {Array.isArray(state.errors) && state.errors.length > 0 && (
                <Alert
                    variant="error"
                    text={state.errors.map((error: any, index: number) => (
                        <div key={index}>
                            {error[0]?.extensions?.response?.status === 500
                                ? CONTRACT_TERM_ERROR_MSG
                                : error}
                        </div>
                    ))}
                />
            )}
            <ContractTermContext.Provider value={{ state, dispatch }}>
                <div className="NrContractTermsForm-term-section">
                    <NrContractTermsFormFields />
                </div>
                {((!isEmpty(state.scheduleIds) && state.contractTermName) ||
                    !isEmpty(state.conditions)) && (
                    <Button
                        disabled={isSavingNrTerm}
                        className="NrContractTermsForm-copy-conditions-link"
                        onClick={() => {
                            setIsCopyConditionsModalOpen(true);
                            dispatch({ type: SET_ERRORS, error: [] });
                        }}
                        variant="quartenary"
                    >
                        Click Here to Copy Term Conditions from Another Term
                    </Button>
                )}
                {((!isEmpty(state.scheduleIds) && state.contractTermName) ||
                    !isEmpty(state.conditions)) && (
                    <div className="NrContractTermsForm-term-conditions-section">
                        <NContractTermsConditionsList />
                    </div>
                )}
                <CopyTermConditionsFormPopup
                    contractType={AbacusContractType.NEIGHBOURING_RIGHTS}
                    isCopyConditionsModalOpen={isCopyConditionsModalOpen}
                    setIsCopyConditionsModalOpen={setIsCopyConditionsModalOpen}
                />
                <DeleteContractTermPopup
                    isDeleteContractTermModalOpen={
                        isDeleteContractTermModalOpen
                    }
                    setIsDeleteContractTermModalOpen={
                        setIsDeleteContractTermModalOpen
                    }
                />
            </ContractTermContext.Provider>
            <div className="NrContractTermsForm-btn-section">
                <div className="NrContractTermsForm-buttons">
                    <LinkButton
                        buttonSize="md"
                        disabled={isSavingNrTerm}
                        label="Cancel"
                        target={getContractDetail(contractId)}
                        variant="default"
                    />
                    <Button
                        className="NrContractTermsForm-save-button"
                        disabled={
                            !(state.conditions.length > 0) || isSavingNrTerm
                        }
                        onClick={() => saveHandler()}
                        variant="primary"
                    >
                        Save
                    </Button>
                </div>
            </div>
        </div>
    );
};

export default NrContractTermsForm;
