import React from 'react';
import { render, screen } from '@testing-library/react';
import { openSelect, selectOption } from 'lib/test-utils/select';
import { usePaymentSearchQuery } from 'src/apollo/queries/payment-search';
import PaymentSearch from '../payment-search';

// Mock the Apollo query hook so the search hooks run for real (we assert the
// filters they end up sending) without hitting the network.
jest.mock('src/apollo/queries/payment-search', () => ({
    usePaymentSearchQuery: jest.fn(),
}));

// Stub the heavy GridTable-backed Table. We only care which props PaymentSearch
// forwards to it. The captured props are asserted via mockTable.
const mockTable = jest.fn();
jest.mock(
    'src/components/payments-list/payment-search/components/table',
    () => ({
        __esModule: true,
        default: (props: { isAccountScoped?: boolean }) => {
            mockTable(props);
            return <div data-testid="payment-table" />;
        },
    })
);

// Page.Toolbar is a layout wrapper from suite-frontend. Render its children
// directly so the toolbar contents (the real FiltersToolbar) are testable.
jest.mock('@theorchard/suite-frontend', () => ({
    ...jest.requireActual('@theorchard/suite-frontend'),
    Page: {
        Toolbar: ({ children }: { children: React.ReactNode }) => (
            <div>{children}</div>
        ),
    },
}));

const contractOptions = [
    { label: 'Contract A - 200001', value: '200001' },
    { label: 'Contract B - 200002', value: '200002' },
];

describe('<PaymentSearch>', () => {
    beforeEach(() => {
        jest.clearAllMocks();
        jest.mocked(usePaymentSearchQuery).mockReturnValue({
            data: undefined,
            loading: false,
            error: undefined,
        });
    });

    describe('global (no accountConfig)', () => {
        it('shows the account text filter and contract text filter', () => {
            render(<PaymentSearch />);

            expect(
                screen.getByPlaceholderText('Filter by Account ID')
            ).toBeDefined();
            expect(
                screen.getByPlaceholderText('Filter by Contract ID')
            ).toBeDefined();
            expect(screen.queryByTestId('ContractDropdown')).toBeNull();
        });

        it('runs the query with no pinned filters and the table ungrouped flag off', () => {
            render(<PaymentSearch />);

            expect(usePaymentSearchQuery).toHaveBeenLastCalledWith({
                filters: {},
            });
            expect(mockTable).toHaveBeenLastCalledWith(
                expect.objectContaining({ isAccountScoped: false })
            );
        });
    });

    describe('account-scoped (accountConfig provided)', () => {
        const renderScoped = () =>
            render(
                <PaymentSearch
                    accountConfig={{ accountId: '123', contractOptions }}
                />
            );

        it('pins the search to the account so the query fires immediately', () => {
            renderScoped();

            expect(usePaymentSearchQuery).toHaveBeenLastCalledWith({
                filters: { account: '123' },
            });
        });

        it('hides the account filter and replaces the contract filter with a dropdown', () => {
            renderScoped();

            expect(
                screen.queryByPlaceholderText('Filter by Account ID')
            ).toBeNull();
            expect(
                screen.queryByPlaceholderText('Filter by Contract ID')
            ).toBeNull();
            expect(screen.getByTestId('ContractDropdown')).toBeDefined();
        });

        it('marks the table as account-scoped', () => {
            renderScoped();

            expect(mockTable).toHaveBeenLastCalledWith(
                expect.objectContaining({ isAccountScoped: true })
            );
        });

        it('filters locally through the contract dropdown without a new query', async () => {
            const item = (id: string, contractId: string) =>
                ({
                    __typename: 'PaymentSearchItem',
                    id,
                    paymentStatus: 'successful',
                    paymentType: 'balance_payment',
                    account: { accountId: '123', accountName: 'A' },
                    contract: { contractId, contractName: contractId },
                    paymentCreatedDate: '2024-01-01',
                    paymentReleaseDate: null,
                }) as never;
            jest.mocked(usePaymentSearchQuery).mockReturnValue({
                data: {
                    paymentSearch: {
                        __typename: 'PaymentSearchResult',
                        items: [item('a', '200001'), item('b', '200002')],
                        totalCount: 2,
                    },
                } as never,
                loading: false,
                error: undefined,
            });

            renderScoped();

            // Table starts with both rows.
            const tableDataOf = (call: number) =>
                mockTable.mock.calls[call][0].data?.paymentSearch?.items?.map(
                    (i: { id: string }) => i.id
                );
            expect(tableDataOf(mockTable.mock.calls.length - 1)).toEqual([
                'a',
                'b',
            ]);

            jest.mocked(usePaymentSearchQuery).mockClear();

            await openSelect('ContractDropdown');
            selectOption(contractOptions[0]); // Contract A - 200001

            // Table now receives only the matching row...
            expect(tableDataOf(mockTable.mock.calls.length - 1)).toEqual(['a']);
            // ...and no query ran with anything but the pinned account.
            expect(usePaymentSearchQuery.mock.calls.length).toBeGreaterThan(0);
            usePaymentSearchQuery.mock.calls.forEach(([arg]) =>
                expect(arg).toEqual({ filters: { account: '123' } })
            );
        });
    });
});
