import React, { useCallback, useMemo, useState } from 'react';
import {
    Alert,
    GridTable,
    Modal,
    Section,
    useToast,
} from '@theorchard/suite-components';
import './styles.scss';
import { Illustration } from '@theorchard/suite-icons';
import { DEFAULT_ITEMS_PER_PAGE } from 'src/constants';
import {
    useProjectTransferJobs,
    type ProjectTransferJob,
} from 'src/apollo/queries/transfer-projects';
import {
    useAccountsByVendorIds,
    useAccountsForTransferSearch,
    useSubaccountNames,
    type AccountName,
} from 'src/apollo/queries/account';
import { useStatementPeriods } from 'src/apollo/queries/statement-periods';
import { useDeleteProjectTransferJob } from 'src/apollo/mutations/delete-project-transfer-job';
import TransferFilters from './transfer-filters';
import TransferListTableColumns, {
    formatPeriodKey,
    TRANSFER_DELETE_DISABLED_REASON,
    type StatementPeriodInfo,
} from './transfer-list-table-columns';
import type {
    GridTableSortBy,
    ListViewItem,
} from '@theorchard/suite-components';

const ALL_ACCOUNTS_OPTION: ListViewItem = { label: 'All Accounts', value: '' };

// Account filter options from a server-side search result. The search hook
// returns accountId as the value, but the jobs filter matches on vendor id,
// so remap the subtitle (vendor id) into the value. "All Accounts" is only
// offered on the empty search, where it reads as the default.
export const buildAccountFilterOptions = (
    options: ListViewItem[],
    term?: string
): ListViewItem[] => {
    const mapped = options.map(o => ({
        label: o.label,
        value: o.subtitle ?? '',
        subtitle: o.subtitle,
    }));
    return term ? mapped : [ALL_ACCOUNTS_OPTION, ...mapped];
};

export const TRANSFER_NOT_VISIBLE_REASON =
    'This transfer is no longer visible on this page. Close and re-check its status.';

// Polling can change a job's status (or drop it from the current page) while
// its delete confirmation modal is open. Recompute whether the pending delete
// is still allowed on every render.
export const getDeletingJobBlockedReason = (
    jobs: ProjectTransferJob[],
    deletingJobId: string | null
): string | undefined => {
    if (!deletingJobId) return undefined;
    const job = jobs.find(j => j.projectTransferJobId === deletingJobId);
    if (!job) return TRANSFER_NOT_VISIBLE_REASON;
    return TRANSFER_DELETE_DISABLED_REASON[job.status];
};

const getSortValue = (t: ProjectTransferJob, key: string): string | number => {
    switch (key) {
        case 'projectTransferJobId':
            return t.projectTransferJobId;
        case 'status':
            return t.status;
        case 'originLabel':
            return t.originLabel.vendorId;
        case 'destinationLabel':
            return t.destinationLabel.vendorId;
        case 'createdAt':
            return t.createdAt;
        case 'transferCompletedOn':
            return t.transferCompletedOn ?? '';
        default:
            return '';
    }
};

const TransferList: React.FC = () => {
    const [searchId, setSearchId] = useState('');
    const [statusFilter, setStatusFilter] = useState<ListViewItem[]>([]);
    const [periodFrom, setPeriodFrom] = useState('');
    const [periodTo, setPeriodTo] = useState('');
    const [accountFilter, setAccountFilter] = useState<
        ListViewItem | undefined
    >(undefined);
    const [sortBy, setSortBy] = useState<GridTableSortBy[]>([]);
    const [page, setPage] = useState(0);
    const [pageSize, setPageSize] = useState(DEFAULT_ITEMS_PER_PAGE);
    const [deletingJobId, setDeletingJobId] = useState<string | null>(null);
    const [deleteError, setDeleteError] = useState<string | null>(null);
    const toast = useToast();
    const { deleteJob, loading: deleting } = useDeleteProjectTransferJob();

    const serverStatus =
        statusFilter.length === 1
            ? (statusFilter[0].value as ProjectTransferJob['status'])
            : undefined;

    const { jobs, totalCount, loading, refetch } = useProjectTransferJobs({
        limit: pageSize,
        offset: page * pageSize,
        status: serverStatus,
    });

    const deleteBlockedReason = getDeletingJobBlockedReason(
        jobs,
        deletingJobId
    );

    const pageVendorIds = useMemo(() => {
        const ids = new Set<number>();
        for (const job of jobs) {
            ids.add(job.originLabel.vendorId);
            ids.add(job.destinationLabel.vendorId);
        }
        return Array.from(ids);
    }, [jobs]);

    const { accounts: pageAccounts, loading: accountsLoading } =
        useAccountsByVendorIds(pageVendorIds);

    const vendorNames = useMemo(
        () =>
            new Map(
                pageAccounts.map((a: AccountName) => [
                    a.vendor.vendorId,
                    {
                        name: a.accountName,
                        accountId: a.accountId,
                        brandName: a.vendor.companyBrand?.name,
                    },
                ])
            ),
        [pageAccounts]
    );

    const subaccountVendorIds = useMemo(() => {
        const ids = new Set<number>();
        for (const job of jobs) {
            for (const label of [job.originLabel, job.destinationLabel]) {
                if (label.subaccountId) ids.add(label.vendorId);
            }
        }
        return Array.from(ids);
    }, [jobs]);

    const subaccountNames = useSubaccountNames(subaccountVendorIds);

    const { search: searchAccounts } = useAccountsForTransferSearch();

    const { data: periodsData } = useStatementPeriods();
    const statementPeriods = useMemo(() => {
        const map = new Map<string, StatementPeriodInfo>();
        const all = [
            ...(periodsData?.abacusStatementPeriods?.recentPeriods ?? []),
            ...(periodsData?.abacusStatementPeriods?.upcomingPeriods ?? []),
        ];
        for (const p of all) map.set(p.statementPeriodName, p);
        return map;
    }, [periodsData]);

    // The period filter offers only the recent (past/current) periods. Upcoming
    // periods are far-future and have the highest ids, so including them would
    // make "Latest"/"Last 12" land on future dates. They stay in the map above
    // so a transfer whose effective period is upcoming still resolves when
    // filtering.
    const periodList = useMemo(
        () =>
            (periodsData?.abacusStatementPeriods?.recentPeriods ?? []).map(
                p => ({
                    statementPeriodId: p.statementPeriodId,
                    statementPeriodName: p.statementPeriodName,
                })
            ),
        [periodsData]
    );

    const onLoadAccountOptions = useCallback(
        async (term?: string) => {
            const { data, totalCount } = await searchAccounts(term);
            return {
                data: buildAccountFilterOptions(data, term),
                totalCount,
            };
        },
        [searchAccounts]
    );

    const handleDelete = (jobId: string) => {
        setDeleteError(null);
        setDeletingJobId(jobId);
    };

    const visibleJobs = useMemo(() => {
        let result = jobs;

        if (searchId.trim()) {
            const term = searchId.trim().toLowerCase();
            result = result.filter(t =>
                t.projectTransferJobId.toLowerCase().includes(term)
            );
        }

        if (statusFilter.length > 1) {
            const selected = new Set(statusFilter.map(s => s.value));
            result = result.filter(t => selected.has(t.status));
        }

        if (periodFrom || periodTo) {
            const fromId = periodFrom
                ? Number(
                      statementPeriods.get(periodFrom)?.statementPeriodId ?? 0
                  )
                : 0;
            const toId = periodTo
                ? Number(
                      statementPeriods.get(periodTo)?.statementPeriodId ??
                          Infinity
                  )
                : Infinity;
            // Ensure from <= to regardless of selection order; Infinity means no upper bound
            const minId = Math.min(fromId, toId === Infinity ? fromId : toId);
            const maxId = toId === Infinity ? Infinity : Math.max(fromId, toId);
            result = result.filter(t => {
                if (!t.revenueCutoffDate) return false;
                const key = formatPeriodKey(t.revenueCutoffDate);
                const period = statementPeriods.get(key);
                if (!period) return false;
                const id = Number(period.statementPeriodId);
                return id >= minId && id <= maxId;
            });
        }

        if (accountFilter?.value) {
            const vendorId = Number(accountFilter.value);
            result = result.filter(
                t =>
                    t.originLabel.vendorId === vendorId ||
                    t.destinationLabel.vendorId === vendorId
            );
        }

        if (sortBy.length > 0) {
            const { key, direction } = sortBy[0];
            result = [...result].sort((a, b) => {
                const aVal = getSortValue(a, key);
                const bVal = getSortValue(b, key);
                if (aVal < bVal) return direction === 'asc' ? -1 : 1;
                if (aVal > bVal) return direction === 'asc' ? 1 : -1;
                return 0;
            });
        }

        return result;
    }, [
        jobs,
        searchId,
        statusFilter,
        periodFrom,
        periodTo,
        accountFilter,
        statementPeriods,
        sortBy,
    ]);

    const isClientFiltered = !!(
        searchId ||
        statusFilter.length > 1 ||
        periodFrom ||
        periodTo ||
        accountFilter?.value
    );

    const handleStatusChange = (value: ListViewItem[]) => {
        setStatusFilter(value);
        setPage(0);
    };

    const handlePeriodChange = (from: string, to: string) => {
        setPeriodFrom(from);
        setPeriodTo(to);
        setPage(0);
    };

    const handleAccountChange = (item: ListViewItem | undefined) => {
        // "All Accounts" (empty value) clears the filter.
        setAccountFilter(item?.value ? item : undefined);
        setPage(0);
    };

    return (
        <div className="TransferList">
            <TransferFilters
                searchValue={searchId}
                statusValue={statusFilter}
                periods={periodList}
                periodFrom={periodFrom}
                periodTo={periodTo}
                accountValue={accountFilter}
                onLoadAccountOptions={onLoadAccountOptions}
                onSearchChange={setSearchId}
                onStatusChange={handleStatusChange}
                onPeriodChange={handlePeriodChange}
                onAccountChange={handleAccountChange}
            />
            <Section className="TransferListTable">
                <Section.Body>
                    <Section.Table>
                        <GridTable
                            name="TransferProjects"
                            stickyHeader
                            bordered
                            variant="zebra"
                            paginated
                            page={page}
                            pageSize={pageSize}
                            totalCount={
                                isClientFiltered
                                    ? visibleJobs.length
                                    : totalCount
                            }
                            onPageChange={setPage}
                            onPageSizeChange={setPageSize}
                            loadingRows={pageSize}
                            columnDefs={TransferListTableColumns(
                                vendorNames,
                                subaccountNames,
                                statementPeriods,
                                handleDelete
                            )}
                            data={loading || accountsLoading ? [] : visibleJobs}
                            loading={loading || accountsLoading}
                            rowKey="projectTransferJobId"
                            emptyStateTitle="No transfers found"
                            emptyStateIcon={<Illustration name="noResults" />}
                            sortBy={sortBy}
                            onSort={setSortBy}
                        />
                    </Section.Table>
                </Section.Body>
            </Section>
            <Modal
                isOpen={!!deletingJobId}
                title={`Delete Transfer ${deletingJobId}?`}
                onRequestClose={() => {
                    setDeletingJobId(null);
                    setDeleteError(null);
                }}
                cancelTitle="Cancel"
                confirmTitle="Delete"
                confirmVariant="danger"
                confirmDisabled={deleting || !!deleteBlockedReason}
                onConfirm={async () => {
                    if (!deletingJobId || deleteBlockedReason) return;
                    try {
                        await deleteJob(deletingJobId);
                        toast(
                            `Transfer ${deletingJobId} has been successfully deleted.`
                        );
                        setDeletingJobId(null);
                        refetch();
                    } catch (e) {
                        if (e instanceof Error) setDeleteError(e.message);
                    }
                }}
            >
                <span>Once you delete this transfer it cannot be undone.</span>
                {deleteBlockedReason && (
                    <Alert
                        variant="error"
                        text={deleteBlockedReason}
                        className="mt-3"
                    />
                )}
                {deleteError && (
                    <Alert
                        variant="error"
                        text={deleteError}
                        className="mt-3"
                    />
                )}
            </Modal>
        </div>
    );
};

export default TransferList;
