import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useApolloClient } from '@apollo/client';
import {
    Alert,
    Button,
    FullscreenModal,
    Tooltip,
} from '@theorchard/suite-components';
import capitalize from 'lodash-es/capitalize';
import {
    useCreateProjectTransfer,
    type CreateTransferTermInput,
} from 'src/apollo/mutations/create-project-transfer';
import {
    useAccountContracts,
    useAccountsForTransferSearch,
    useAccountSubaccounts,
    type AccountForTransfer,
} from 'src/apollo/queries/account';
import {
    useProjectById,
    useProjectProducts,
    useProjectSearch,
    type ProductWithTracks,
    type ProjectSummary,
} from 'src/apollo/queries/product-search';
import { GetProjectTransferJobsDocument } from 'src/apollo/queries/transfer-projects';
import DetailsStep from './details-step';
import SetupTermsStep, {
    type TermsMode,
    type TransferTermDraft,
} from './setup-terms-step';
import { validateTransferTerms } from './term-validation';
import type { ProjectOption, TwoLineOption } from '../types';

type Step = 'details' | 'terms';

export const buildProjects = (
    products: ProductWithTracks[]
): ProjectOption[] => {
    const byId = new Map<string, ProjectOption>();
    for (const p of products) {
        const rawId = p.project?.projectId;
        if (rawId == null) continue;
        // The product API returns projectId as an Int; normalize to a string so
        // it matches the picker's projectId (a String from the project lookup).
        const projectId = String(rawId);
        let proj = byId.get(projectId);
        if (!proj) {
            proj = {
                id: projectId,
                name: p.project?.projectName ?? p.productName,
                projectCode: p.project?.projectCode,
                upcs: [],
            };
            byId.set(projectId, proj);
        }
        proj.upcs.push({
            upc: p.upc,
            productName: p.productName,
            tracks: p.tracks.map(t => ({
                id: t.isrc,
                title: t.trackName,
                isrc: t.isrc,
            })),
        });
    }
    return Array.from(byId.values());
};

const productCountLabel = (count: number | null | undefined): string | null =>
    count == null ? null : `${count} ${count === 1 ? 'Product' : 'Products'}`;

// Subaccount picker options, used for both the origin and destination sides.
const buildSubaccountOptions = (
    subaccounts: { subaccountId: number; name: string }[]
): TwoLineOption[] =>
    subaccounts.map(s => ({
        label: s.name,
        value: String(s.subaccountId),
        subtitle: String(s.subaccountId),
    }));

// Options for the project picker from a searchProject (or project-by-id)
// result. The subtitle carries the project id, code, and the index's accurate
// product count.
export const buildProjectSearchOptions = (
    projects: ProjectSummary[]
): TwoLineOption[] =>
    projects.map(p => ({
        label: p.projectName ?? String(p.projectId),
        value: String(p.projectId),
        subtitle: [
            String(p.projectId),
            p.projectCode,
            productCountLabel(p.productCount),
        ]
            .filter(Boolean)
            .join(' · '),
    }));

// searchProject only matches name/code/artist, so a numeric Project ID is
// looked up directly via project(projectId). Surface it only when it belongs
// to the selected origin vendor, and to the chosen subaccount when one is in
// scope (project(projectId) is not scoped), so the picker never offers a
// project from another account or a different subaccount (ACC-10579 /
// ACC-10586). When the account has no subaccounts, subaccountId is undefined
// and the guard is vendor-only.
export const buildIdLookupOption = (
    project: ProjectSummary | null,
    vendorId: number | undefined,
    subaccountId: number | undefined
): TwoLineOption[] =>
    project &&
    vendorId !== undefined &&
    project.vendorId === vendorId &&
    (subaccountId === undefined || project.subaccountId === subaccountId)
        ? buildProjectSearchOptions([project])
        : [];

// Merge two option lists, deduped by projectId, first list winning. Used to
// prepend an exact Project ID match ahead of the name-search results without
// listing the same project twice.
export const mergeProjectOptions = (
    primaryOptions: TwoLineOption[],
    secondaryOptions: TwoLineOption[]
): TwoLineOption[] => {
    const seen = new Set(primaryOptions.map(o => o.value));
    return [
        ...primaryOptions,
        ...secondaryOptions.filter(o => !seen.has(o.value)),
    ];
};

// Build the selected project's full product/track tree from the products
// fetched for it. Returns undefined until a project is picked, or while the
// fetched products do not yet include it.
export const selectProject = (
    products: ProductWithTracks[],
    projectId: string | undefined
): ProjectOption | undefined =>
    projectId
        ? buildProjects(products).find(p => p.id === projectId)
        : undefined;

interface CreateTransferFormProps {
    isOpen: boolean;
    onClose: () => void;
    onSuccess: (jobId: string) => void;
}

const CreateTransferForm: React.FC<CreateTransferFormProps> = ({
    isOpen,
    onClose,
    onSuccess,
}) => {
    const [step, setStep] = useState<Step>('details');
    const [fromAccount, setFromAccount] = useState<
        AccountForTransfer | undefined
    >();
    const [toAccount, setToAccount] = useState<
        AccountForTransfer | undefined
    >();
    const [toSubaccountId, setToSubaccountId] = useState<string | undefined>();
    const [fromSubaccountId, setFromSubaccountId] = useState<
        string | undefined
    >();
    const [projectId, setProjectId] = useState<string | undefined>();
    const [projectName, setProjectName] = useState<string | undefined>();
    const [contractId, setContractId] = useState<string | undefined>();
    const [terms, setTerms] = useState<TransferTermDraft[]>([]);
    const [termsMode, setTermsMode] = useState<TermsMode>('label');
    const [submitting, setSubmitting] = useState(false);
    const [submitError, setSubmitError] = useState<string | null>(null);

    const client = useApolloClient();
    const { search: searchAccounts, getAccount } =
        useAccountsForTransferSearch();
    const fromAccountId = fromAccount?.accountId;
    const toAccountId = toAccount?.accountId;
    const fromVendorId = fromAccount?.vendor.vendorId;
    const { contracts: toAccountContracts, loading: contractsLoading } =
        useAccountContracts(toAccountId);
    const { subaccounts: toAccountSubaccounts } = useAccountSubaccounts(
        toAccount?.vendor.vendorId
    );
    const { subaccounts: fromAccountSubaccounts } =
        useAccountSubaccounts(fromVendorId);
    const { create: createProjectTransfer } = useCreateProjectTransfer();

    const searchProjects = useProjectSearch(fromVendorId, fromSubaccountId);
    const getProjectById = useProjectById();

    // The origin subaccount selector appears only when the account has
    // subaccounts (like the destination side). When it does, picking one is
    // required before the project search runs, and it scopes that search;
    // otherwise the project search is vendor-wide.
    const fromSubaccountRequired = fromAccountSubaccounts.length > 0;
    const fromSubaccountReady =
        !fromSubaccountRequired || fromSubaccountId !== undefined;

    // The selected project's product/track tree is fetched on demand by name
    // (the only product query that takes a project), then narrowed to the
    // picked projectId so it stays exact even when project names collide
    // (ACC-10579).
    const { products: pickedProducts } = useProjectProducts(
        fromVendorId,
        projectName
    );
    const selectedProject = useMemo(
        () => selectProject(pickedProducts, projectId),
        [pickedProducts, projectId]
    );
    const selectedContract = toAccountContracts.find(
        c => c.contractId === contractId
    );

    // A label-mode transfer needs only the project id (that is all the mutation
    // takes); the product/track tree is fetched separately and used solely by
    // the custom-terms step. Gate Next on the picked project, never on that
    // tree, so a slow or incomplete product search can't strand a valid
    // transfer behind the button (ACC-10579).
    const detailsComplete = !!(fromAccountId && toAccountId && projectId);

    // The contracts query finished and the destination account has no
    // contracts. contractId can never be set in that case, so Next stays
    // disabled — surface why instead of the misleading "Select a contract".
    const zeroContracts =
        !contractsLoading && !!toAccountId && toAccountContracts.length === 0;

    // Single source of truth for the Next button: whether it is blocked and the
    // reason to show on hover. Ordered most- to least-specific so the message
    // matches the actual cause rather than a generic prompt.
    const nextBlocked = !detailsComplete || !contractId || submitting;
    let nextBlockedReason = '';
    if (!nextBlocked) {
        nextBlockedReason = '';
    } else if (!detailsComplete) {
        nextBlockedReason = 'Complete all required fields to proceed';
    } else if (contractsLoading) {
        nextBlockedReason = 'Loading contracts...';
    } else if (zeroContracts) {
        nextBlockedReason = 'The destination account has no contracts';
    } else if (!contractId) {
        nextBlockedReason = 'Select a contract to proceed to terms';
    } else {
        nextBlockedReason = 'Submission in progress';
    }

    const onLoadFromOptions = useCallback(
        async (term?: string) => await searchAccounts(term),
        [searchAccounts]
    );

    const onLoadToOptions = useCallback(
        async (term?: string) => await searchAccounts(term, fromAccountId),
        [searchAccounts, fromAccountId]
    );

    const onLoadProjectOptions = useCallback(
        async (term?: string) => {
            const trimmed = term?.trim();
            // Type-to-search; when the account has subaccounts the search is
            // gated on (and scoped to) the chosen one, so offer nothing until a
            // term exists and a required subaccount is picked (ACC-10586).
            if (!trimmed || !fromSubaccountReady)
                return { data: [], totalCount: 0 };

            const nameOptions = buildProjectSearchOptions(
                await searchProjects(trimmed)
            );

            // searchProject matches name/code/artist, never project_id, so a
            // purely numeric term may be a Project ID it can't reach. Look that
            // id up directly and surface it when it belongs to the origin
            // vendor, and the chosen subaccount when one is in scope (ACC-10579
            // / ACC-10586).
            if (/^\d+$/.test(trimmed)) {
                const idOptions = buildIdLookupOption(
                    await getProjectById(trimmed),
                    fromVendorId,
                    fromSubaccountId !== undefined
                        ? Number(fromSubaccountId)
                        : undefined
                );
                const data = mergeProjectOptions(idOptions, nameOptions);
                return { data, totalCount: data.length };
            }

            return { data: nameOptions, totalCount: nameOptions.length };
        },
        [
            searchProjects,
            getProjectById,
            fromVendorId,
            fromSubaccountId,
            fromSubaccountReady,
        ]
    );

    const contractOptions: TwoLineOption[] = toAccountContracts.map(c => ({
        label: c.contractName,
        value: c.contractId,
        subtitle: capitalize(c.contractType),
    }));

    // When the destination account has exactly one contract, pick it for the
    // user (the selector is disabled in that case).
    useEffect(() => {
        if (toAccountContracts.length === 1 && !contractId) {
            setContractId(toAccountContracts[0].contractId);
        }
    }, [toAccountContracts, contractId]);

    const toSubaccountOptions = buildSubaccountOptions(toAccountSubaccounts);
    const fromSubaccountOptions = buildSubaccountOptions(
        fromAccountSubaccounts
    );

    // Validate the custom terms with the same rules as the contract detail page.
    // Errors disable Submit Transfer (with a tooltip explaining why); an invalid
    // Label's share also turns its input red live (see SetupTermsStep).
    const termErrors = validateTransferTerms(terms, termsMode);

    // Each selection invalidates the choices that depend on it. The reset
    // cascade is defined once here (bottom-up) and reused by the change
    // handlers and close, so adding a field means updating one helper rather
    // than every handler.
    const resetTerms = () => {
        setTerms([]);
        setTermsMode('label');
    };
    const resetProjectSelection = () => {
        setProjectId(undefined);
        setProjectName(undefined);
    };
    const resetBelowToAccount = () => {
        setToSubaccountId(undefined);
        setContractId(undefined);
        resetTerms();
    };
    const resetBelowFromAccount = () => {
        setFromSubaccountId(undefined);
        resetProjectSelection();
        setToAccount(undefined);
        resetBelowToAccount();
    };

    const handleClose = () => {
        setStep('details');
        setFromAccount(undefined);
        resetBelowFromAccount();
        setSubmitError(null);
        setSubmitting(false);
        onClose();
    };

    const handleFromAccountChange = (item: TwoLineOption | undefined) => {
        setFromAccount(item ? getAccount(item.value) : undefined);
        resetBelowFromAccount();
    };

    // Changing the origin subaccount re-scopes the project search, so clear any
    // project picked under the previous one.
    const handleFromSubaccountChange = (id: string | undefined) => {
        setFromSubaccountId(id);
        resetProjectSelection();
    };

    const handleProjectChange = (item: TwoLineOption | undefined) => {
        setProjectId(item?.value);
        setProjectName(item?.label);
    };

    const handleToAccountChange = (item: TwoLineOption | undefined) => {
        setToAccount(item ? getAccount(item.value) : undefined);
        resetBelowToAccount();
    };

    const handleContractChange = (id: string | undefined) => {
        setContractId(id);
        resetTerms();
    };

    const toTermsInput = (
        draft: TransferTermDraft
    ): CreateTransferTermInput => ({
        contractId: contractId!,
        name: draft.name.trim() || undefined,
        termType: draft.type === 'PRODUCT' ? 'product' : 'track',
        attachments: draft.attachments,
        attachmentRelations: toAccount
            ? { labelIds: [String(toAccount.vendor.vendorId)] }
            : undefined,
        conditions: draft.conditions.map(c => ({
            priority: c.priority,
            termRate: c.termRate ?? '',
            conditions: {
                countries: c.conditions.countries,
                stores: c.conditions.stores,
                transactionTypes: c.conditions.transactionTypes,
            },
        })),
    });

    const handleSubmit = async () => {
        if (!fromAccount || !toAccount || !projectId || !contractId) return;

        // Belt-and-suspenders: the button is disabled while invalid.
        if (termErrors.length > 0) return;

        setSubmitting(true);
        setSubmitError(null);

        try {
            const termInputs: CreateTransferTermInput[] =
                termsMode === 'custom' && terms.length > 0
                    ? terms.map(toTermsInput)
                    : [
                          {
                              contractId,
                              termType: 'label' as const,
                              conditions: [],
                          },
                      ];

            const job = await createProjectTransfer(
                {
                    projectId,
                    originVendorId: String(fromAccount.vendor.vendorId),
                    // No originSubaccountId here by design: the backend
                    // resolves the origin subaccount from the project itself
                    // (fromSubaccountId only scopes the project search above).
                    destinationVendorId: String(toAccount.vendor.vendorId),
                    destinationSubaccountId: toSubaccountId,
                },
                termInputs
            );

            await client.refetchQueries({
                include: [GetProjectTransferJobsDocument],
            });
            onSuccess(job.projectTransferJobId);
            handleClose();
        } catch (err) {
            setSubmitError(
                err instanceof Error
                    ? err.message
                    : "We couldn't queue this transfer. Please try again."
            );
            setSubmitting(false);
        }
    };

    const isDetailsStep = step === 'details';
    const isTermsStep = step === 'terms';

    const headerTitle = isTermsStep
        ? `Setup Terms: ${selectedContract?.contractName ?? ''}`
        : 'Create New Transfer';

    return (
        <FullscreenModal
            layout="fluid"
            className="CreateTransferForm"
            isOpen={isOpen}
        >
            <FullscreenModal.Header title={headerTitle}>
                <Button variant="ghost" onClick={handleClose}>
                    Cancel
                </Button>
            </FullscreenModal.Header>

            <FullscreenModal.Body>
                {submitError && <Alert variant="error" text={submitError} />}

                {isDetailsStep && (
                    <DetailsStep
                        fromAccount={fromAccount}
                        toAccount={toAccount}
                        projectId={projectId}
                        contractId={contractId}
                        toSubaccountId={toSubaccountId}
                        toSubaccountOptions={toSubaccountOptions}
                        fromSubaccountId={fromSubaccountId}
                        fromSubaccountOptions={fromSubaccountOptions}
                        contractOptions={contractOptions}
                        onLoadFromOptions={onLoadFromOptions}
                        onLoadToOptions={onLoadToOptions}
                        onLoadProjectOptions={onLoadProjectOptions}
                        onFromAccountChange={handleFromAccountChange}
                        onFromSubaccountChange={handleFromSubaccountChange}
                        onToAccountChange={handleToAccountChange}
                        onProjectChange={handleProjectChange}
                        onContractChange={handleContractChange}
                        onToSubaccountChange={setToSubaccountId}
                    />
                )}

                {isTermsStep && (
                    <SetupTermsStep
                        project={selectedProject}
                        terms={terms}
                        termsMode={termsMode}
                        onTermsChange={setTerms}
                        onTermsModeChange={setTermsMode}
                    />
                )}
            </FullscreenModal.Body>

            <FullscreenModal.Footer>
                {isDetailsStep && (
                    <>
                        {nextBlocked ? (
                            <Tooltip
                                id="next-btn-tooltip"
                                placement="top"
                                message={nextBlockedReason}
                            >
                                <span style={{ display: 'inline-block' }}>
                                    <Button
                                        variant="secondary"
                                        disabled
                                        style={{ pointerEvents: 'none' }}
                                    >
                                        Next
                                    </Button>
                                </span>
                            </Tooltip>
                        ) : (
                            <Button
                                variant="secondary"
                                onClick={() => setStep('terms')}
                            >
                                Next
                            </Button>
                        )}
                    </>
                )}
                {isTermsStep && (
                    <>
                        <Button
                            variant="secondary"
                            disabled={submitting}
                            onClick={() => setStep('details')}
                        >
                            Back
                        </Button>
                        {termErrors.length > 0 ? (
                            <Tooltip
                                id="submit-transfer-tooltip"
                                placement="top"
                                message={
                                    <>
                                        {termErrors.map(error => (
                                            <div key={error}>{error}</div>
                                        ))}
                                    </>
                                }
                            >
                                <span style={{ display: 'inline-block' }}>
                                    <Button
                                        variant="primary"
                                        disabled
                                        style={{ pointerEvents: 'none' }}
                                    >
                                        Submit Transfer
                                    </Button>
                                </span>
                            </Tooltip>
                        ) : (
                            <Button
                                variant="primary"
                                disabled={submitting}
                                onClick={handleSubmit}
                            >
                                {submitting
                                    ? 'Submitting...'
                                    : 'Submit Transfer'}
                            </Button>
                        )}
                    </>
                )}
            </FullscreenModal.Footer>
        </FullscreenModal>
    );
};

export default CreateTransferForm;
