import React, { useMemo } from 'react';
import { Alert, Section } from '@theorchard/suite-components';
import { useContractLabelTerms } from 'src/apollo/queries/contract';
import { useStoreList } from 'src/apollo/queries/stores';
import {
    useTransactionTypes,
    useTransactionTypeGroups,
} from 'src/apollo/queries/transaction-types';
import { TransactionTypeGroupAdmin } from 'src/apollo/definitions/globalTypes';
import { TermsDetail } from 'src/components/shared/terms-detail';
import type { ContractTerm } from 'src/apollo/queries/contract';
import type { TransferTerm } from 'src/apollo/queries/transfer-terms';
import { labelTerritories } from '../term-options';
import type {
    TermsDetailGroup,
    TermsDetailItem,
} from 'src/components/shared/terms-detail';

const formatList = (values: string[] | undefined, allLabel = 'All'): string =>
    !values || values.length === 0 ? allLabel : values.join(', ');

const lookupLabels = (ids: string[], map: Map<string, string>): string[] =>
    ids.map(id => map.get(id) ?? id);

interface PresentationalGroup {
    id: string;
    name: string;
}

// Minimal condition shape shared by transfer terms and contract label terms.
interface GroupableCondition {
    termRate: number;
    conditions: {
        transactionTypes?: string[] | null;
        stores?: string[] | null;
        countries?: string[] | null;
    } | null;
}

const OTHER_GROUP_ID = '__other__';

interface GroupRow {
    rate: number;
    txnTypeIds: string[];
    stores: string[];
    countries: string[];
}

// Buckets a term's conditions into the contract's presentational transaction-
// type groups (Digital, Physical, ...), matching the contract detail page. A
// condition's transaction types are split across the groups they belong to; a
// condition with no transaction types applies to every type not explicitly
// carved out by another condition. Each group shows a single rate, or "Multiple
// Rates" when it spans several; types with no group fall into "Other".
const consolidateGroups = (
    conditions: GroupableCondition[],
    orderedGroups: PresentationalGroup[],
    txnTypeToGroupId: Map<string, string>,
    txnTypeMap: Map<string, string>,
    storeMap: Map<string, string>
): TermsDetailGroup[] => {
    const explicitTxnTypes = new Set(
        conditions.flatMap(c => c.conditions?.transactionTypes ?? [])
    );
    const remainingTxnTypes = Array.from(txnTypeMap.keys()).filter(
        tt => !explicitTxnTypes.has(tt)
    );

    const rowsByGroup = new Map<string, GroupRow[]>();
    const push = (groupId: string, row: GroupRow) => {
        const rows = rowsByGroup.get(groupId) ?? [];
        rows.push(row);
        rowsByGroup.set(groupId, rows);
    };

    for (const cond of conditions) {
        const set = cond.conditions ?? {};
        const stores = set.stores ?? [];
        const countries = set.countries ?? [];
        const txnTypes =
            set.transactionTypes && set.transactionTypes.length > 0
                ? set.transactionTypes
                : remainingTxnTypes;

        const subsetByGroup = new Map<string, string[]>();
        for (const txnType of txnTypes) {
            const groupId = txnTypeToGroupId.get(txnType) ?? OTHER_GROUP_ID;
            const ids = subsetByGroup.get(groupId) ?? [];
            ids.push(txnType);
            subsetByGroup.set(groupId, ids);
        }
        for (const [groupId, ids] of subsetByGroup) {
            push(groupId, {
                rate: cond.termRate,
                txnTypeIds: ids,
                stores,
                countries,
            });
        }
    }

    const build = (groupId: string, name: string): TermsDetailGroup | null => {
        const rows = rowsByGroup.get(groupId);
        if (!rows || rows.length === 0) return null;
        const rates = Array.from(new Set(rows.map(r => r.rate)));
        const title =
            rates.length === 1
                ? `${name} • ${rates[0]}%`
                : `${name} • Multiple Rates, Expand for Details`;
        return {
            id: groupId,
            title,
            conditions: rows.map((r, idx) => ({
                id: String(idx + 1),
                transactionType: formatList(
                    lookupLabels(r.txnTypeIds, txnTypeMap)
                ),
                service: formatList(lookupLabels(r.stores, storeMap)),
                country: formatList(labelTerritories(r.countries)),
                rate: String(r.rate),
            })),
        };
    };

    const result: TermsDetailGroup[] = [];
    orderedGroups.forEach(g => {
        const group = build(g.id, g.name);
        if (group) result.push(group);
    });
    const other = build(OTHER_GROUP_ID, 'Other');
    if (other) result.push(other);
    return result;
};

const TYPE_LABEL: Record<string, string> = {
    label: 'Label Term',
    product: 'Product Term',
    track: 'Track Term',
};

// Maps a product/track transfer term into the shared TermsDetail shape, with
// its conditions consolidated into presentational groups (same as label terms).
const toTermsDetailItem = (
    term: TransferTerm,
    orderedGroups: PresentationalGroup[],
    txnTypeToGroupId: Map<string, string>,
    txnTypeMap: Map<string, string>,
    storeMap: Map<string, string>
): TermsDetailItem => {
    const isProduct = term.termType === 'product';
    const attachments = term.attachments ?? [];
    const attachmentCount = attachments.length;
    const attachmentLabel = isProduct ? 'product' : 'track';
    const noun = isProduct ? 'Product' : 'Track';
    const countLabel = `${attachmentCount} ${noun}${
        attachmentCount === 1 ? '' : 's'
    }`;

    return {
        id: term.projectTransferTermId,
        name: term.name?.trim() || TYPE_LABEL[term.termType] || 'Term',
        // The full attachment list lives in the sidecar. The transfer term id
        // is temporary (a real id is assigned once the transfer runs), so it's
        // not shown.
        metadata: <span>{countLabel}</span>,
        sidecar: {
            heading: `${isProduct ? 'Products' : 'Tracks'} (${attachmentCount})`,
            items: attachments.map(attachment => ({
                label: attachment,
            })),
            emptyTooltip: `There are no ${attachmentLabel}s associated with this term.`,
            noMatchText: `No ${attachmentLabel}s found for that search input`,
        },
        groups: consolidateGroups(
            term.conditions,
            orderedGroups,
            txnTypeToGroupId,
            txnTypeMap,
            storeMap
        ),
    };
};

// Maps a destination-contract label term into the shared TermsDetail shape,
// consolidating its conditions into presentational transaction-type groups.
const contractLabelTermToItem = (
    term: ContractTerm,
    orderedGroups: PresentationalGroup[],
    txnTypeToGroupId: Map<string, string>,
    txnTypeMap: Map<string, string>,
    storeMap: Map<string, string>
): TermsDetailItem => ({
    id: term.contractTermId,
    name: term.contractTermName?.trim() || 'Label Term',
    // Label terms already exist on the contract; just reference the term id.
    metadata: <span>Term ID: {term.contractTermId}</span>,
    groups: consolidateGroups(
        term.conditions,
        orderedGroups,
        txnTypeToGroupId,
        txnTypeMap,
        storeMap
    ),
});

interface TransferTermsSectionProps {
    terms: TransferTerm[] | undefined;
    contractName?: string;
    loading?: boolean;
}

const TransferTermsSection: React.FC<TransferTermsSectionProps> = ({
    terms,
    contractName,
    loading,
}) => {
    const { data: storesData } = useStoreList();
    const { data: transactionTypeData } = useTransactionTypes();
    const { data: groupsData } = useTransactionTypeGroups(
        TransactionTypeGroupAdmin.PRESENTATIONAL
    );

    // Label terms live on the destination contract; the transfer term only
    // carries a contractId reference, so fetch the contract's label terms.
    const labelContractId = terms?.find(t => t.contractId)?.contractId;
    const { labelTerms: contractLabelTerms } =
        useContractLabelTerms(labelContractId);

    const txnTypeMap = useMemo(
        () =>
            new Map(
                (transactionTypeData?.transactionTypes ?? []).map(
                    ({
                        txnTypeId,
                        txnTypeCode,
                        txnTypeName,
                    }: {
                        txnTypeId: string;
                        txnTypeCode: string;
                        txnTypeName: string;
                    }) => [txnTypeId, `${txnTypeCode} - ${txnTypeName}`]
                )
            ),
        [transactionTypeData]
    );

    const storeMap = useMemo(
        () =>
            new Map(
                (storesData?.deliveryStoresV2?.items ?? []).map(
                    ({ id, name }: { id: string; name: string }) => [id, name]
                )
            ),
        [storesData]
    );

    // Presentational transaction-type groups (ordered) and a txnType -> group
    // lookup, used to consolidate label-term conditions like the contract page.
    const { orderedGroups, txnTypeToGroupId } = useMemo(() => {
        const groups = groupsData?.transactionTypeGroups ?? [];
        const map = new Map<string, string>();
        groups.forEach(group => {
            group.transactionTypes?.forEach(txnType => {
                if (txnType?.txnTypeId)
                    map.set(
                        txnType.txnTypeId,
                        group.referenceTransactionTypeGroupId
                    );
            });
        });
        return {
            orderedGroups: groups.map(group => ({
                id: group.referenceTransactionTypeGroupId,
                name: group.transactionTypeGroupName,
            })),
            txnTypeToGroupId: map,
        };
    }, [groupsData]);

    if (loading) {
        return (
            <Section data-testid="transferTermsLoading">
                <Section.Header>
                    <Section.Title>Terms to be Applied</Section.Title>
                </Section.Header>
                <Section.Body>
                    <span className="text-muted">Loading terms...</span>
                </Section.Body>
            </Section>
        );
    }

    if (!terms || terms.length === 0) {
        return (
            <Section data-testid="transferTermsLabelOnly">
                <Section.Header>
                    <Section.Title>Terms to be Applied</Section.Title>
                </Section.Header>
                <Section.Body>
                    {contractName ? (
                        <Alert
                            variant="information"
                            text={`Label terms from "${contractName}" will apply to all products and tracks in this transfer.`}
                        />
                    ) : (
                        <Alert
                            variant="warn"
                            text="This transfer has no destination contract - no revenue will be earned."
                        />
                    )}
                </Section.Body>
            </Section>
        );
    }

    const itemsOfType = (type: TransferTerm['termType']) =>
        terms
            .filter(t => t.termType === type)
            .map(t =>
                toTermsDetailItem(
                    t,
                    orderedGroups,
                    txnTypeToGroupId,
                    txnTypeMap,
                    storeMap
                )
            );

    const labelTerms = contractLabelTerms.map(t =>
        contractLabelTermToItem(
            t,
            orderedGroups,
            txnTypeToGroupId,
            txnTypeMap,
            storeMap
        )
    );
    const productTerms = itemsOfType('product');
    const trackTerms = itemsOfType('track');

    return (
        <section className="TransferDetail-projects">
            <h2 className="TransferDetail-projects-title h2">
                Terms to be applied
            </h2>
            <TermsDetail
                title="Label Terms"
                terms={labelTerms}
                emptyMessage="No Label Terms have been defined for this transfer."
                testId="labelTransferTermsDetail"
            />
            <TermsDetail
                title="Product Terms"
                terms={productTerms}
                emptyMessage="No Product Terms have been defined for this transfer."
                testId="productTransferTermsDetail"
            />
            <TermsDetail
                title="Track Terms"
                terms={trackTerms}
                emptyMessage="No Track Terms have been defined for this transfer."
                testId="trackTransferTermsDetail"
            />
        </section>
    );
};

export default TransferTermsSection;
