import React, { useContext, useCallback } from 'react';
import { Button, Section } from '@theorchard/suite-components';
import { groupBy, capitalize } from 'lodash-es';
import { Link, useParams } from 'react-router-dom';
import { TransactionTypeGroupAdmin } from 'src/apollo/definitions/globalTypes';
import { useTransactionTypeGroups } from 'src/apollo/queries/transaction-types';
import RestrictedByAbacusProfileWrapper from 'src/components/restricted-by-abacus-profile-wrapper';
import { CONTRACT_TERM_TYPES } from 'src/constants';
import { ContractTermContext } from 'src/contexts/contract-term-context';
import { createNewTerms } from 'src/urls/frontend-royalties';
import {
    formatCountriesList,
    formatStoresList,
    formatTransactionTypesList,
} from 'src/utils/contract-detail-formatters';
import ContractTermPresentationalGroups from './contract-term-detail-presentational-groups';
import ContractTermDetailPresentationalHeader from './contract-term-detail-presentational-header';
import type { GetContractQuery } from 'src/apollo/queries/contract/__generated__/get-contract';
import type {
    ContractTermDetailFormattedPresentational,
    ContractTermFormattedPresentational,
    ContractTermDetailPresentational,
    ContractTermException,
} from 'src/types/contract-term';
import type { TransactionTypes } from 'src/utils/contract-detail-formatters';

type AbacusContract = NonNullable<GetContractQuery['abacusContract']>;
export type AbacusContractTerm = AbacusContract['contractTerms'] extends
    | (infer T)[]
    | null
    ? T
    : null;

type Conditions = AbacusContractTerm['conditions'] extends (infer T)[] | null
    ? T
    : null;

interface GroupedConditions {
    presentationalGroup: PresentationGroupWithUnformattedConditions;
}

interface PresentationGroupWithUnformattedConditions {
    id: string;
    name: string;
    conditions: Conditions[];
}

export interface AttachmentDetail {
    value: string;
    name: string;
    artistName?: string;
}

export interface ContractTermsDetailPropTypes {
    contractTerms: AbacusContractTerm[];
    termType: string;
}

export const retrieveDefaultLabelShare = (
    conditions: ContractTermDetailFormattedPresentational[]
): number | number[] | null => {
    const conditionsWithAll = conditions.filter(
        condition =>
            condition.country[0] === 'All' && condition.service[0] === 'All'
    );

    if (conditionsWithAll.length === 0) {
        return null;
    } else if (conditionsWithAll.length === 1) {
        return conditionsWithAll[0].labelShare;
    } else {
        const uniqueLabelShares: number[] = [
            ...new Set(
                conditionsWithAll
                    .map(condition => condition.labelShare)
                    .filter((share): share is number => share !== null)
            ),
        ];
        return uniqueLabelShares.length > 1
            ? uniqueLabelShares
            : (uniqueLabelShares[0] ?? null);
    }
};

export const formatExceptions = (
    conditions: ContractTermDetailFormattedPresentational[],
    defaultLabelShare: number | number[] | null
): ContractTermException[] =>
    conditions
        .filter(
            ({ country, service, labelShare }) =>
                labelShare !== null &&
                !(country[0] === 'All' && service[0] === 'All') &&
                (Array.isArray(defaultLabelShare)
                    ? !defaultLabelShare.includes(labelShare)
                    : defaultLabelShare !== labelShare)
        )
        .map(({ country, service, labelShare }) => ({
            country,
            service,
            labelShare: labelShare as number,
        }));

const ContractTermsDetailPresentational: React.FC<
    ContractTermsDetailPropTypes
> = ({ contractTerms, termType }) => {
    const { contractId } = useParams<{ contractId: string }>();
    const NO_TERMS_MESSAGE = `No ${capitalize(termType)} Terms have been defined for this contract.`;

    const { state } = useContext(ContractTermContext);
    const storeList = state?.stores ?? {};
    const allTransactionTypes: TransactionTypes = state?.transactionTypes ?? [];
    const termTypeCapitalised: string =
        termType.charAt(0).toUpperCase() + termType.slice(1);

    const { data: refTransactionGroups, loading: refTransactionGroupsLoading } =
        useTransactionTypeGroups(TransactionTypeGroupAdmin.PRESENTATIONAL);

    const groupAndFormatContractTermConditions = useCallback(
        (conditions: Conditions[]): ContractTermDetailPresentational[] => {
            if (!refTransactionGroups || refTransactionGroupsLoading) {
                return [];
            }

            const transactionTypeGroups = [
                ...refTransactionGroups.transactionTypeGroups,
            ].sort(
                (a, b) =>
                    parseInt(a.referenceTransactionTypeGroupId, 10) -
                    parseInt(b.referenceTransactionTypeGroupId, 10)
            );

            const groupedConditions: GroupedConditions[] =
                transactionTypeGroups.map(group => ({
                    presentationalGroup: {
                        id: group.referenceTransactionTypeGroupId,
                        name: group.transactionTypeGroupName,
                        conditions: [],
                    },
                }));

            const groupIndexMap: Record<string, number> = Object.fromEntries(
                groupedConditions.map((item, index) => [
                    item.presentationalGroup.id,
                    index,
                ])
            );

            const existingTransactionTypes = new Set(
                conditions.flatMap(c =>
                    c.conditions?.transactionTypesV2?.map(tt =>
                        parseInt(tt?.txnTypeId ?? '', 10)
                    )
                )
            );

            for (const condition of conditions) {
                let transactionTypesV2 =
                    condition.conditions?.transactionTypesV2 || [];

                if (transactionTypesV2.length === 0) {
                    transactionTypesV2 = allTransactionTypes.filter(
                        t =>
                            t &&
                            !existingTransactionTypes.has(
                                parseInt(t.txnTypeId, 10)
                            )
                    );
                }

                const groupedByTransactionGroup = transactionTypesV2.reduce(
                    (acc, txn) => {
                        if (txn?.transactionTypeGroup) {
                            const groupId =
                                txn.transactionTypeGroup
                                    .referenceTransactionTypeGroupId;
                            if (!acc[groupId]) {
                                acc[groupId] = {
                                    ...condition,
                                    conditions: {
                                        ...condition.conditions,
                                        countries:
                                            condition.conditions?.countries ??
                                            [],
                                        stores:
                                            condition.conditions?.stores ?? [],
                                        transactionTypes:
                                            condition.conditions
                                                ?.transactionTypes ?? null,
                                        transactionTypesV2:
                                            condition.conditions?.transactionTypesV2?.filter(
                                                item =>
                                                    item?.transactionTypeGroup
                                                        ?.referenceTransactionTypeGroupId ===
                                                    groupId
                                            ) ?? [],
                                    },
                                };
                            }
                        }
                        return acc;
                    },
                    {} as Record<string, Conditions>
                );

                for (const [groupId, groupedCondition] of Object.entries(
                    groupedByTransactionGroup
                )) {
                    const groupIndex = groupIndexMap[groupId];
                    if (groupIndex !== undefined) {
                        groupedConditions[
                            groupIndex
                        ].presentationalGroup.conditions.push({
                            ...groupedCondition,
                        });
                    }
                }
            }

            const filteredGroupedConditions = groupedConditions.filter(
                g => g.presentationalGroup.conditions.length > 0
            );
            filteredGroupedConditions.forEach(group =>
                group.presentationalGroup.conditions.sort(
                    (a, b) => (a.priority ?? 0) - (b.priority ?? 0)
                )
            );

            return filteredGroupedConditions.map(({ presentationalGroup }) => {
                const {
                    name,
                    id,
                    conditions: groupConds,
                } = presentationalGroup;

                const formattedConditions: ContractTermDetailFormattedPresentational[] =
                    groupConds.map((cond, index) => {
                        const priority = cond.priority;
                        const termRate = cond.termRate ?? null;
                        const storesString = formatStoresList(
                            cond.conditions?.stores ?? [],
                            storeList
                        );
                        const transactionTypesString =
                            formatTransactionTypesList(
                                cond.conditions?.transactionTypesV2 ?? []
                            );
                        const countriesString = formatCountriesList(
                            cond.conditions?.countries ?? []
                        );

                        return {
                            transactionType: transactionTypesString,
                            service: storesString,
                            country: countriesString,
                            labelShare: termRate,
                            priority,
                            id: index + 1,
                        };
                    });
                let defaultLabelShare =
                    retrieveDefaultLabelShare(formattedConditions);

                if (defaultLabelShare === null) {
                    defaultLabelShare =
                        formattedConditions[formattedConditions.length - 1]
                            .labelShare;
                }
                const exceptions = formatExceptions(
                    formattedConditions,
                    defaultLabelShare
                );

                return {
                    presentationalGroup: {
                        name,
                        id,
                        defaultLabelShare: defaultLabelShare,
                        conditions: formattedConditions,
                        exceptions,
                    },
                };
            });
        },
        [
            refTransactionGroups,
            refTransactionGroupsLoading,
            allTransactionTypes,
            storeList,
        ]
    );

    const retrieveAttachmentDetails = (
        contractTermsUnformattedDataList: AbacusContractTerm[]
    ): AttachmentDetail[] => {
        const allAttachments = [
            ...new Set(
                contractTermsUnformattedDataList.flatMap(
                    obj => obj.attachmentsObjects ?? []
                )
            ),
        ];
        let listItems: AttachmentDetail[] = [];
        listItems = allAttachments
            .map(item => {
                if (item.__typename === 'Product') {
                    if (item.upc) {
                        return {
                            value: item.upc,
                            name: item.productName ?? 'Unknown Product',
                            artistName:
                                item.labelParticipations[0]?.labelParticipant
                                    ?.name ?? 'Unknown Artist',
                        };
                    }
                } else if (item.__typename === 'UnknownProduct') {
                    if (item.upc) {
                        return {
                            value: item.upc,
                            name: 'Unknown Product',
                            artistName: 'Unknown Artist',
                        };
                    }
                } else if (item.__typename === 'Isrc') {
                    return {
                        value: item.isrc,
                        name: item.globalSoundRecording?.name ?? 'Uknown Track',
                        artistName:
                            item.globalSoundRecording?.globalParticipants[0]
                                ?.name ?? 'Unknown Artist',
                    };
                } else if (item.__typename === 'NrContribution') {
                    return {
                        value: item.nrContributor.name ?? '-',
                        name: '-',
                        artistName: 'Unknown Artist',
                    };
                }
                return undefined;
            })
            .filter(Boolean) as AttachmentDetail[];
        return listItems;
    };

    const formatContractTermData = useCallback(
        (
            unformatted: AbacusContractTerm
        ): ContractTermFormattedPresentational => {
            const formattedTermId = unformatted.contractTermId;
            const catalogLength = unformatted.attachments?.length;

            let formattedLabelIds: string[] | null | undefined = [];
            if (unformatted.termType === CONTRACT_TERM_TYPES.LABEL) {
                formattedLabelIds = unformatted.attachments;
            } else {
                formattedLabelIds = unformatted.attachmentsRelations?.labelIds;
            }

            const formattedContractTermName = unformatted.contractTermName;
            const conditions = unformatted.conditions ?? [];

            const details = groupAndFormatContractTermConditions(conditions);

            return {
                termId: formattedTermId,
                catalogLength,
                labelIds: formattedLabelIds,
                contractTermName: formattedContractTermName,
                details,
                attachments: unformatted.attachments,
                termType: unformatted.termType,
            };
        },
        [groupAndFormatContractTermConditions]
    );

    const renderTermBlock = useCallback(
        (
            contractTermsUnformattedDataList: AbacusContractTerm[],
            type: string
        ) => {
            const allAttachmentsWithDetails = retrieveAttachmentDetails(
                contractTermsUnformattedDataList
            );
            const contractTermsFormattedDataList =
                contractTermsUnformattedDataList.map(item =>
                    formatContractTermData(item)
                );

            const contractTermsFormattedDataMap = groupBy(
                contractTermsFormattedDataList,
                item => item.termId
            );

            return Object.values(contractTermsFormattedDataMap).map(
                (
                    formattedDataArray: ContractTermFormattedPresentational[],
                    index: number
                ) => {
                    const firstTerm = formattedDataArray[0];
                    return (
                        <Section key={`termBlock${index}`}>
                            {(firstTerm.labelIds ?? []).map(
                                (labelId, labelIndex) => (
                                    <ContractTermDetailPresentationalHeader
                                        data-testid="contractTermPresentationalHeader"
                                        key={labelIndex}
                                        contractTermLabelIndex={labelIndex}
                                        contractTermFormattedDataMapIndex={
                                            index
                                        }
                                        labelId={labelId}
                                        termType={type}
                                        contractTermFormattedDataObject={
                                            firstTerm
                                        }
                                        contractId={contractId ?? ''}
                                        allAttachments={
                                            allAttachmentsWithDetails
                                        }
                                    />
                                )
                            )}
                            {(!firstTerm.labelIds ||
                                firstTerm.labelIds.length === 0) && (
                                <ContractTermDetailPresentationalHeader
                                    key={'contract-label'}
                                    data-testid="contractTermPresentationalHeader"
                                    contractTermLabelIndex={index}
                                    contractTermFormattedDataMapIndex={index}
                                    labelId={''}
                                    termType={type}
                                    contractTermFormattedDataObject={firstTerm}
                                    contractId={contractId ?? ''}
                                    allAttachments={allAttachmentsWithDetails}
                                />
                            )}
                            <ContractTermPresentationalGroups
                                key="presentationalGroups"
                                className="ContractTermsConditions-labelInfoBlock-presentationalGroups"
                                contractTermFormattedDataList={
                                    formattedDataArray
                                }
                            />
                        </Section>
                    );
                }
            );
        },
        [formatContractTermData, contractId]
    );

    return (
        <div>
            <Section>
                <Section.Header data-testid="contractTermHeading">
                    <div className="d-flex flex-column ContractTermsConditions-presentational-header">
                        <div
                            className="d-flex flex-row align-items-center"
                            data-testid="parentTermType"
                        >
                            <Section.Title className="h3">
                                {termTypeCapitalised} Terms
                            </Section.Title>
                            <RestrictedByAbacusProfileWrapper>
                                <Link
                                    to={createNewTerms(
                                        contractId ?? '',
                                        termType
                                    )}
                                    data-testid={`contractTermsAddPageLink${capitalize(termType)}`}
                                    className="ml-auto"
                                >
                                    <Button
                                        variant="primary"
                                        className="ContractTermsConditions-customAddButton"
                                        data-testid={`contractTermsAddButton${capitalize(termType)}`}
                                    >
                                        <span>&nbsp;+ Add&nbsp;</span>
                                    </Button>
                                </Link>
                            </RestrictedByAbacusProfileWrapper>
                        </div>
                    </div>
                </Section.Header>
                <Section.Body>
                    {contractTerms.length > 0 ? (
                        renderTermBlock(contractTerms, termType)
                    ) : (
                        <Section>
                            <Section.Body>
                                <span
                                    className="mb-2 text-muted"
                                    data-testid="contractTermsNoTermsMessage"
                                >
                                    {NO_TERMS_MESSAGE}
                                </span>
                            </Section.Body>
                        </Section>
                    )}
                </Section.Body>
            </Section>
        </div>
    );
};

export default ContractTermsDetailPresentational;
