import { ApolloClient } from '@apollo/client';
import { getContract } from 'src/apollo/queries/contract';
import getReferenceSigningEntities from 'src/apollo/queries/reference-signing-entity/get-reference-signing-entities.gql';
import {
    BulkEditEntryBase,
    BulkSchema,
    runInBatches,
} from './common-bulk-edit-schema';
import type { GridTableColumnDefinition } from '@theorchard/suite-components';
import React from 'react';
import { Link } from 'react-router-dom';
import { getContractDetail } from 'src/urls/frontend-royalties';
import abacusUpdateContractSigningEntityId from 'src/apollo/mutations/contract/update-contract-signing-entity.gql';
import updateAccountPaymentTerm from 'src/apollo/mutations/account-payment-term/update-account-payment-term.gql';
import {
    GET_CONTRACT_MAX_PARALLEL,
    UPDATE_ACCOUNT_PAYMENT_ENTITY_MAX_PARALLEL,
    UPDATE_CONTRACT_SIGNING_ENTITY_MAX_PARALLEL,
} from 'src/components/contract-bulk-edit/common/edit-schema/constants';

const CONTRACT_SIGNING_ENTITY_TABLE_HEADERS: GridTableColumnDefinition<ContractSigningEntityBulkEditEntry>[] =
    [
        {
            name: 'contractId',
            title: 'Contract Id',
        },
        {
            name: 'contractName',
            title: 'Contract Name',
            Cell: ({ data: { contractId, contractName } }: any) => (
                <div>
                    {contractName ? (
                        <Link
                            key={contractId}
                            to={getContractDetail(contractId)}
                        >
                            {contractName}
                        </Link>
                    ) : (
                        '-'
                    )}
                </div>
            ),
        },
        {
            name: 'currentSigningEntityId',
            title: 'Current Signing Entity',
            Cell: ({
                data: { currentSigningEntityId, currentSigningEntityName },
            }: any) => (
                <div>
                    {currentSigningEntityId && currentSigningEntityName
                        ? `(${currentSigningEntityId}) ${currentSigningEntityName}`
                        : '-'}
                </div>
            ),
        },
        {
            name: 'signingEntityIdToSet',
            title: 'Signing Entity to Set',
            Cell: ({
                data: { signingEntityIdToSet, signingEntityNameToSet },
            }: any) => (
                <div>
                    {signingEntityIdToSet && signingEntityNameToSet
                        ? `(${signingEntityIdToSet}) ${signingEntityNameToSet}`
                        : '-'}
                </div>
            ),
        },
        {
            name: 'currentPaymentEntityId',
            title: 'Current Payment Entity',
            Cell: ({
                data: { currentPaymentEntityId, currentPaymentEntityName },
            }: any) => (
                <div>
                    {currentPaymentEntityId && currentPaymentEntityName
                        ? `(${currentPaymentEntityId}) ${currentPaymentEntityName}`
                        : '-'}
                </div>
            ),
        },
        {
            name: 'paymentEntityIdToSet',
            title: 'Payment Entity to Set',
            Cell: ({
                data: { paymentEntityIdToSet, paymentEntityNameToSet },
            }: any) => (
                <div>
                    {paymentEntityIdToSet && paymentEntityNameToSet
                        ? `(${paymentEntityIdToSet}) ${paymentEntityNameToSet}`
                        : '-'}
                </div>
            ),
        },
        {
            name: 'currentSapProfitCenterId',
            title: 'Current SAP Profit Center',
            Cell: ({
                data: { currentSapProfitCenterId, currentSapProfitCenterName },
            }: any) => (
                <div>
                    {currentSapProfitCenterId && currentSapProfitCenterName
                        ? `(${currentSapProfitCenterId}) ${currentSapProfitCenterName}`
                        : '-'}
                </div>
            ),
        },
        {
            name: 'sapProfitCenterIdToSet',
            title: 'SAP Profit Center to Set',
            Cell: ({
                data: { sapProfitCenterIdToSet, sapProfitCenterNameToSet },
            }: any) => (
                <div>
                    {sapProfitCenterIdToSet && sapProfitCenterNameToSet
                        ? `(${sapProfitCenterIdToSet}) ${sapProfitCenterNameToSet}`
                        : '-'}
                </div>
            ),
        },
    ];

export interface ContractSigningEntityBulkEditEntry extends BulkEditEntryBase {
    id: string;
    contractId: string;
    contractName: string | undefined | null;
    currentSigningEntityId: string | undefined | null;
    currentSigningEntityName: string | undefined | null;
    signingEntityIdToSet: string | undefined | null;
    signingEntityNameToSet: string | undefined | null;

    accountId: string | undefined | null;
    accountPaymentTermId: string | undefined | null;
    currentPaymentEntityId: string | undefined | null;
    currentPaymentEntityName: string | undefined | null;
    paymentEntityIdToSet: string | undefined | null;
    paymentEntityNameToSet: string | undefined | null;

    currentSapProfitCenterId: string | undefined | null;
    currentSapProfitCenterName: string | undefined | null;
    sapProfitCenterIdToSet: string | undefined | null;
    sapProfitCenterNameToSet: string | undefined | null;
}

async function updateSigningEntities(
    apolloClient: ApolloClient<object>,
    entries: ContractSigningEntityBulkEditEntry[],
    onFail: (
        e: ContractSigningEntityBulkEditEntry,
        err: any,
        message: string | null
    ) => void
) {
    const successfulContractIds = new Set<string>();

    await runInBatches(
        entries,
        UPDATE_CONTRACT_SIGNING_ENTITY_MAX_PARALLEL,
        async entry => {
            if (!entry.signingEntityIdToSet) {
                onFail(
                    entry,
                    new Error('No signing entity id to update.'),
                    'No signing entity id to update.'
                );
                return;
            }

            if (!entry.contractName) {
                onFail(
                    entry,
                    new Error('No contract to update.'),
                    'No contract to update..'
                );
                return;
            }

            try {
                await apolloClient.mutate({
                    mutation: abacusUpdateContractSigningEntityId,
                    variables: {
                        contractId: entry.contractId,
                        signingEntityId: entry.signingEntityIdToSet,
                        sapProfitCenterId: entry.sapProfitCenterIdToSet,
                    },
                });
                successfulContractIds.add(entry.contractId);
            } catch (error) {
                onFail(entry, error, 'Error updating contract signing entity.');
            }
        }
    );

    return successfulContractIds;
}

async function updatePaymentEntity(
    apolloClient: ApolloClient<object>,
    entriesToProcess: ContractSigningEntityBulkEditEntry[],
    onSuccess: (e: ContractSigningEntityBulkEditEntry) => void,
    onFail: (
        e: ContractSigningEntityBulkEditEntry,
        err: any,
        message: string | null
    ) => void
) {
    const accountPaymentTermGroups = new Map<
        string,
        ContractSigningEntityBulkEditEntry[]
    >();

    for (const entry of entriesToProcess) {
        if (entry.currentPaymentEntityId == entry.paymentEntityIdToSet) {
            // payment entity is consistent
            onSuccess(entry);
            continue;
        }
        if (!entry.accountPaymentTermId || !entry.paymentEntityIdToSet) {
            onFail(
                entry,
                null,
                'No account payment term id or payment entity id to update.'
            );
            continue;
        }
        const group =
            accountPaymentTermGroups.get(entry.accountPaymentTermId) ?? [];
        group.push(entry);
        accountPaymentTermGroups.set(entry.accountPaymentTermId, group);
    }

    await runInBatches(
        Array.from(accountPaymentTermGroups.entries()),
        UPDATE_ACCOUNT_PAYMENT_ENTITY_MAX_PARALLEL,
        async ([accountPaymentTermId, group]) => {
            try {
                await apolloClient.mutate({
                    mutation: updateAccountPaymentTerm,
                    variables: {
                        accountPaymentTermId,
                        paymentEntityId: group[0].paymentEntityIdToSet,
                    },
                });
                group.forEach(e => onSuccess(e));
            } catch (error) {
                group.forEach(e =>
                    onFail(
                        e,
                        error,
                        'Signing entity updated, but error updating account payment entity.'
                    )
                );
            }
        }
    );
}

export const contractSigningEntitySchema: BulkSchema<ContractSigningEntityBulkEditEntry> =
    {
        id: 'contractSigningEntity',
        displayName: 'Contract Signing Entity',
        columns: CONTRACT_SIGNING_ENTITY_TABLE_HEADERS,
        expectedFileColumns: [
            {
                name: 'contract_id',
                description: 'Contract ID to update',
            },
            {
                name: 'signing_entity_id',
                description: 'Signing entity ID to set.',
            },
            {
                name: 'signing_entity_name',
                description:
                    'Signing entity name to set. Lower priority than signing_entity_id.',
            },
            {
                name: 'sap_profit_center_id',
                description:
                    'SAP profit center ID to set. Must be an authorized profit center for the target signing entity.',
            },
        ],
        async toEntries(apolloClient: ApolloClient<object>, rows: any[]) {
            const referenceSigningEntityData = await apolloClient.query({
                query: getReferenceSigningEntities,
            });

            async function mapJsonToEditEntries(
                parsedJsonArray: any[]
            ): Promise<ContractSigningEntityBulkEditEntry[]> {
                const mapEntry = async (
                    entry: any
                ): Promise<ContractSigningEntityBulkEditEntry> => {
                    const contractId: string = entry['contract_id'];
                    const signingEntityId = entry['signing_entity_id'];
                    const signingEntityName = entry['signing_entity_name'];
                    const sapProfitCenterId = entry['sap_profit_center_id'];

                    const referenceSigningEntity =
                        referenceSigningEntityData.data.abacusReferenceSigningEntities.items.find(
                            (entity: any) => {
                                if (signingEntityId) {
                                    return (
                                        entity.referenceSigningEntityId ==
                                        signingEntityId
                                    );
                                }
                                if (signingEntityName) {
                                    return (
                                        entity.legalName.trim().toLowerCase() ==
                                        signingEntityName.trim().toLowerCase()
                                    );
                                }
                            }
                        );

                    const authorizedProfitCenter = sapProfitCenterId
                        ? referenceSigningEntity?.authorizedProfitCenters?.find(
                              (pc: any) =>
                                  pc.referenceSapProfitCenterId ==
                                  sapProfitCenterId
                          )
                        : undefined;

                    const { data: contractData } = await apolloClient.query({
                        query: getContract,
                        variables: {
                            contractId,
                            groupAdmin: 'PRESENTATIONAL',
                        },
                        fetchPolicy: 'network-only',
                    });

                    const sapProfitCenterInvalid =
                        !!sapProfitCenterId && !authorizedProfitCenter;

                    if (
                        !contractData.abacusContract ||
                        !referenceSigningEntity ||
                        sapProfitCenterInvalid
                    ) {
                        return {
                            id: contractId,
                            contractId,
                            contractName: undefined,
                            currentSigningEntityId: undefined,
                            currentSigningEntityName: undefined,
                            signingEntityIdToSet: undefined,
                            signingEntityNameToSet: undefined,
                            accountId: undefined,
                            accountPaymentTermId: undefined,
                            currentPaymentEntityId: undefined,
                            currentPaymentEntityName: undefined,
                            paymentEntityIdToSet: undefined,
                            paymentEntityNameToSet: undefined,
                            currentSapProfitCenterId: undefined,
                            currentSapProfitCenterName: undefined,
                            sapProfitCenterIdToSet: undefined,
                            sapProfitCenterNameToSet: undefined,
                        };
                    }

                    const { account } = contractData.abacusContract;

                    return {
                        id: contractId,
                        contractId,
                        contractName: contractData.abacusContract.contractName,
                        currentSigningEntityId:
                            contractData.abacusContract.referenceSigningEntity
                                .referenceSigningEntityId,
                        currentSigningEntityName:
                            contractData.abacusContract.referenceSigningEntity
                                .legalName,
                        signingEntityIdToSet:
                            referenceSigningEntity.referenceSigningEntityId ||
                            signingEntityId,
                        signingEntityNameToSet:
                            referenceSigningEntity.legalName ||
                            signingEntityName,
                        accountId: account?.accountId,
                        accountPaymentTermId:
                            account?.accountPaymentTerm?.accountPaymentTermId,
                        currentPaymentEntityId:
                            account?.accountPaymentTerm?.paymentEntity
                                ?.referencePaymentEntityId,
                        currentPaymentEntityName:
                            account?.accountPaymentTerm?.paymentEntity
                                ?.paymentEntityName,
                        paymentEntityIdToSet:
                            referenceSigningEntity.referencePaymentEntity
                                ?.referencePaymentEntityId,
                        paymentEntityNameToSet:
                            referenceSigningEntity.referencePaymentEntity
                                ?.paymentEntityName,
                        currentSapProfitCenterId:
                            contractData.abacusContract.sapProfitCenter
                                ?.referenceSapProfitCenterId,
                        currentSapProfitCenterName:
                            contractData.abacusContract.sapProfitCenter
                                ?.displayName,
                        sapProfitCenterIdToSet:
                            authorizedProfitCenter?.referenceSapProfitCenterId,
                        sapProfitCenterNameToSet:
                            authorizedProfitCenter?.displayName,
                    };
                };

                return await runInBatches(
                    parsedJsonArray,
                    GET_CONTRACT_MAX_PARALLEL,
                    mapEntry
                );
            }

            return await mapJsonToEditEntries(rows);
        },
        async apply(apolloClient, entries, onSuccess, onFail) {
            const contractIdsWithSuccessSigningEntityUpdate =
                await updateSigningEntities(apolloClient, entries, onFail);
            const successfulEntries = entries.filter(e =>
                contractIdsWithSuccessSigningEntityUpdate.has(e.contractId)
            );
            await updatePaymentEntity(
                apolloClient,
                successfulEntries,
                onSuccess,
                onFail
            );
        },
    };
