import React from 'react';
import * as apollo from '@apollo/client';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { EMPTY_CHAR } from '@theorchard/accounting-apps-shared';
import { createIdentity, renderInAppContext } from '@theorchard/suite-testing';
import { StatementPeriodAdjustmentFileUsers } from 'src/__fixtures__/graphql/statement-period-adjustment-file';
import {
    StatementPeriodAdjustmentFilesList,
    StatementPeriodAdjustmentFiles,
} from 'src/__fixtures__/graphql/statement-period-adjustment-files-list';
import * as adjustmentQuery from 'src/apollo/queries/adjustment';
import * as abacusEventMutation from 'src/apollo/mutations/abacus-event';
import * as abacusStateMutation from 'src/apollo/mutations/abacus-state';
import * as adjustmentMutation from 'src/apollo/mutations/statement-period-adjustment-file';
import { ADJUSTMENTS_LIST_ERROR_MESSAGE } from 'src/apollo/type-constants/adjustment';
import {
    AdjustmentsList,
    AdjustmentsListPropTypes,
} from 'src/components/adjustments/adjustments-list';
import {
    ABACUS_PROFILE,
    NO_ADJUSTMENT_RESULTS,
    USER_FEATURES,
} from 'src/constants';
import type { GetStatementPeriodAdjustmentFilesListQuery } from 'src/apollo/queries/adjustment/__generated__/get-statement-period-adjustments-files-list';

describe('<AdjustmentsList>', () => {
    let requestSpy: any;

    afterEach(jest.restoreAllMocks);

    beforeEach(() => {
        requestSpy = jest
            .spyOn(adjustmentQuery, 'useStatementPeriodAdjustmentFilesList')
            .mockReturnValue({
                data: StatementPeriodAdjustmentFilesList as GetStatementPeriodAdjustmentFilesListQuery,
                error: undefined,
                loading: false,
                refetch: jest.fn(),
            });
    });

    const mockFeatures = {
        [USER_FEATURES.ABACUS_ADMIN_PAGES_CONVERT_TO_TSX]: false,
    };

    const props: AdjustmentsListPropTypes = {
        setRetryBatch: jest.fn(),
    };

    const render = (
        features: { [x: number]: boolean } = mockFeatures,
        defaultProps: AdjustmentsListPropTypes = props
    ) =>
        renderInAppContext(<AdjustmentsList {...defaultProps} />, {
            identity: createIdentity({
                features,
                profileType: ABACUS_PROFILE,
                id: 'Jane User',
            }),
        });

    it('requests a list of adjustments on render', () => {
        render();
        expect(requestSpy).toHaveBeenCalled();
    });

    it('renders a table headers', () => {
        render();
        const tableHeaders = [
            'Status',
            'Total Adjustments & Expenses',
            'Batch Id',
            'File Name',
            'Statement Period',
            'Date Added',
            'Added By',
            'Date Approved',
            'Approved By',
            'Date Applied',
            'Total Amount (MUL)',
        ];
        tableHeaders.forEach((header: any) =>
            expect(screen.getAllByText(header)).toBeDefined()
        );
    });

    it('renders a list of adjustment files', () => {
        const bodyText: any = [];
        render();
        document
            .querySelectorAll('div.GridTable-cell')
            .forEach((body: any) => bodyText.push(body.textContent));

        expect(bodyText).toEqual([
            'Not Approved',
            '10',
            '268',
            'Adjustment File 4.xlsx',
            'November 2024',
            '2023-11-23',
            'Joe User',
            EMPTY_CHAR,
            EMPTY_CHAR,
            EMPTY_CHAR,
            '1,000.01',
            'Approved',
            '7',
            '266',
            'Adjustment File 3.xlsx',
            'November 2024',
            '2023-11-22',
            'Joe User',
            '2023-11-22',
            'Joe User',
            EMPTY_CHAR,
            '-912.09',
            'Applied',
            '8',
            '262',
            'Adjustment File 2.xlsx',
            'October 2024',
            '2023-11-21',
            'Joe User',
            '2023-11-22',
            'Test User',
            '2023-11-23',
            '29,012.10',
            'Error',
            '8',
            '261',
            'Adjustment File 1.xlsx',
            'October 2024',
            '2023-11-21',
            'Joe User',
            '2023-11-22',
            'Test User',
            '2023-11-23',
            '-1,000.01',
        ]);

        expect(screen.getAllByTestId('DotGlyphIcon').length).toEqual(
            StatementPeriodAdjustmentFilesList
                .abacusStatementPeriodAdjustmentFilesList.items.length
        );
    });

    it('shows message when there are no adjustments', () => {
        jest.spyOn(
            adjustmentQuery,
            'useStatementPeriodAdjustmentFilesList'
        ).mockReturnValue({
            data: {
                abacusStatementPeriodAdjustmentFilesList: {
                    items: [],
                    totalCount: 0,
                },
            },
            error: undefined,
            loading: false,
            refetch: jest.fn(),
        });
        render();
        expect(screen.getByText(NO_ADJUSTMENT_RESULTS)).toBeDefined();
    });

    it('renders BATCH ID links', () => {
        render();

        const link1 = screen.getByTestId('batchPageLink268') as HTMLLinkElement;
        const link2 = screen.getByTestId('batchPageLink266') as HTMLLinkElement;
        const link3 = screen.getByTestId('batchPageLink262') as HTMLLinkElement;

        expect(link1.href).toContain(`/adjustments/268`);
        expect(link2.href).toContain(`/adjustments/266`);
        expect(link3.href).toContain(`/adjustments/262`);
    });

    it('sorts table when sorting icon is clicked', () => {
        render();
        const sortIconsDown = screen.getAllByTestId('ArrowDownGlyphIcon');
        expect(sortIconsDown).toBeDefined();

        fireEvent.click(sortIconsDown[0]);
        const sortUpIcons = screen.getAllByTestId('ArrowUpGlyphIcon');
        expect(sortUpIcons).toBeDefined();

        fireEvent.click(sortUpIcons[0]);
        expect(sortIconsDown).toBeDefined();
    });

    it('renders error message', () => {
        const graphQLError = new apollo.ApolloError({
            graphQLErrors: undefined,
            networkError: null,
            errorMessage: 'GraphQL error',
        });
        jest.spyOn(
            adjustmentQuery,
            'useStatementPeriodAdjustmentFilesList'
        ).mockReturnValue({
            data: {
                abacusStatementPeriodAdjustmentFilesList: {
                    items: [],
                    totalCount: 0,
                },
            },
            error: graphQLError,
            loading: false,
            refetch: jest.fn(),
        });

        render();
        expect(screen.getByText(ADJUSTMENTS_LIST_ERROR_MESSAGE)).toBeDefined();
        expect(screen.getByText(NO_ADJUSTMENT_RESULTS)).toBeDefined();
    });

    it('renders filters', () => {
        render();
        expect(screen.getByPlaceholderText('Filter by Batch ID')).toBeDefined();
        expect(
            screen.getByTestId('Dropdown_statementPeriodAdjustmentFileStatus')
        ).toBeDefined();
    });

    it('renders "Clear Filter" link', () => {
        render();
        fireEvent.keyDown(screen.getByRole('combobox'), { key: 'ArrowDown' });
        fireEvent.click(screen.getAllByText('Not Approved')[0]);
        expect(screen.getAllByText('Clear filters')).toBeDefined();
    });

    it('shows error status when a file has encountered an error', () => {
        render();

        const errorStatus = screen.getByText('Error');
        const statusSymbols = screen.getAllByTestId('DotGlyphIcon');
        const statusSymbolClasses = statusSymbols.map(
            statusSymbol => statusSymbol.parentElement?.className
        );

        expect(errorStatus).toBeInTheDocument();
        expect(statusSymbolClasses).toContain('error');
    });

    it('renders "Clear Filter" link on selecting added by option', async () => {
        jest.spyOn(
            adjustmentQuery,
            'useStatementPeriodAdjustmentFilesUsers'
        ).mockReturnValue({
            data: StatementPeriodAdjustmentFileUsers,
            loading: false,
            error: undefined,
        });

        render();

        const addedBySelect = screen.getAllByRole('combobox', {
            hidden: true,
        })[3];
        fireEvent.click(addedBySelect);

        const selectFirstOption = await screen.findByText('Adjustments Script');
        fireEvent.click(selectFirstOption);

        expect(screen.getByText('Clear filters')).toBeDefined();
    });

    describe('auto-generated column feature flag', () => {
        it('renders Auto-Generated column header when feature flag is enabled', () => {
            render({
                [USER_FEATURES.ABACUS_AUTO_GENERATE_ADJUSTMENTS_FLOWTHROUGH]: true,
            });
            expect(screen.getAllByText('Auto-Generated')).toBeDefined();
        });

        it('does not render Auto-Generated column header when feature flag is disabled', () => {
            render();
            expect(screen.queryByText('Auto-Generated')).toBeNull();
        });
    });
});

describe('<AutoGeneratedAdjustmentsList>', () => {
    let requestSpy: any;
    let softDeleteStatementPeriodAdjustmentFileRequestSpy: any;
    let createAbacusEventRequestSpy: any;
    let resetAbacusStateRequestSpy: any;

    const mockSoftDeleteStatementPeriodAdjustmentFile = jest
        .fn()
        .mockResolvedValue({
            data: {
                abacusSoftDeleteStatementPeriodAdjustmentFile: {
                    deleted: true,
                },
            },
        });
    const mockCreateAbacusEvent = jest.fn().mockResolvedValue({});
    const mockResetAbacusStates = jest.fn().mockResolvedValue([]);

    afterEach(jest.restoreAllMocks);

    beforeEach(() => {
        softDeleteStatementPeriodAdjustmentFileRequestSpy = jest
            .spyOn(
                adjustmentMutation,
                'useSoftDeleteStatementPeriodAdjustmentFile'
            )
            .mockReturnValue(mockSoftDeleteStatementPeriodAdjustmentFile);
        requestSpy = jest
            .spyOn(adjustmentQuery, 'useStatementPeriodAdjustmentFilesList')
            .mockReturnValue({
                data: StatementPeriodAdjustmentFiles as any,
                error: undefined,
                loading: false,
                refetch: jest.fn(),
            });
        createAbacusEventRequestSpy = jest
            .spyOn(abacusEventMutation, 'useCreateAbacusEvent')
            .mockReturnValue(mockCreateAbacusEvent);
        resetAbacusStateRequestSpy = jest
            .spyOn(abacusStateMutation, 'useResetAbacusStates')
            .mockReturnValue(mockResetAbacusStates);
    });

    const mockFeatures = {
        [USER_FEATURES.ABACUS_AUTO_GENERATE_ADJUSTMENTS_FLOWTHROUGH]: true,
    };

    const props: AdjustmentsListPropTypes = {
        setRetryBatch: jest.fn(),
    };

    const render = (
        features: { [x: number]: boolean } = mockFeatures,
        defaultProps: AdjustmentsListPropTypes = props
    ) =>
        renderInAppContext(<AdjustmentsList {...defaultProps} />, {
            identity: createIdentity({
                features,
                profileType: ABACUS_PROFILE,
                id: 'Jane User',
            }),
        });

    it('requests a list of adjustments on render', () => {
        render();
        expect(requestSpy).toHaveBeenCalled();
    });

    it('renders a table headers', () => {
        render();
        const tableHeaders = [
            'Status',
            'Total Adjustments & Expenses',
            'Batch Id',
            'File Name',
            'Statement Period',
            'Date Added',
            'Added By',
            'Date Approved',
            'Approved By',
            'Date Applied',
            'Total Amount (MUL)',
            'Auto-Generated',
        ];
        tableHeaders.forEach((header: any) =>
            expect(screen.getAllByText(header)).toBeDefined()
        );
    });

    it('renders a list of adjustment files', () => {
        const bodyText: any = [];
        render();
        document
            .querySelectorAll('div.GridTable-cell')
            .forEach((body: any) => bodyText.push(body.textContent));

        expect(bodyText).toEqual([
            'Not Approved',
            '0',
            '268',
            'Adjustment File 4.xlsx',
            'November 2024',
            '2023-11-23',
            'Joe User',
            EMPTY_CHAR,
            EMPTY_CHAR,
            EMPTY_CHAR,
            '0.00',
            'Yes',
            'Generating',
            '-',
            '266',
            'Adjustment File 3.xlsx',
            'November 2024',
            '2023-11-22',
            'Joe User',
            EMPTY_CHAR,
            EMPTY_CHAR,
            EMPTY_CHAR,
            '0.00',
            'Yes',
            'Failed to Generate',
            '-',
            '261',
            'Adjustment File 2.xlsx',
            'October 2024',
            '2023-11-21',
            'Joe User',
            EMPTY_CHAR,
            EMPTY_CHAR,
            EMPTY_CHAR,
            '0.00',
            'Yes',
            'Applied',
            '8',
            '262',
            'Adjustment File 1.xlsx',
            'October 2024',
            '2023-11-21',
            'Joe User',
            '2023-11-22',
            'Test User',
            '2023-11-23',
            '-1,000.01',
            'No',
        ]);

        expect(screen.getAllByTestId('DotGlyphIcon').length).toEqual(3);

        expect(screen.getByTestId('LoadingSpinner')).toBeDefined();
    });

    it('shows message when there are no adjustments', () => {
        jest.spyOn(
            adjustmentQuery,
            'useStatementPeriodAdjustmentFilesList'
        ).mockReturnValue({
            data: {
                abacusStatementPeriodAdjustmentFilesList: {
                    items: [],
                    totalCount: 0,
                },
            },
            error: undefined,
            loading: false,
            refetch: jest.fn(),
        });
        render();
        expect(screen.getByText(NO_ADJUSTMENT_RESULTS)).toBeDefined();
    });

    it('shows popover on click of failed batch status icon', () => {
        render();
        const warningGlyphIcons = screen.getAllByTestId('WarningGlyphIcon');
        fireEvent.click(warningGlyphIcons[1]);
        expect(screen.getByText('Failed to Generate Batch')).toBeDefined();
        expect(screen.getByText('Delete Batch')).toBeDefined();
        expect(screen.getByText('Retry Generation')).toBeDefined();
    });

    it('shows popover on click of 0 flowthrough adjustments batch status icon', () => {
        render();
        const warningGlyphIcons = screen.getAllByTestId('WarningGlyphIcon');
        fireEvent.click(warningGlyphIcons[0]);
        expect(
            screen.getByText('No flowthrough adjustments generated')
        ).toBeDefined();
        expect(screen.getByText('Delete Batch')).toBeDefined();
        expect(screen.queryByText('Retry Generation')).toBeNull();
    });

    it('on click of delete batch button requests delete mutation', async () => {
        render();
        const warningGlyphIcons = screen.getAllByTestId('WarningGlyphIcon');
        fireEvent.click(warningGlyphIcons[1]);

        const deleteBatchButton = screen.getByText('Delete Batch');
        fireEvent.click(deleteBatchButton);
        expect(
            softDeleteStatementPeriodAdjustmentFileRequestSpy
        ).toHaveBeenCalled();

        await waitFor(() => {
            expect(
                screen.getByText('Batch 261 has been successfully deleted.')
            ).toBeDefined();
        });
    });

    it('on click of retry generation button requests reset and create mutations', async () => {
        render();
        const warningGlyphIcons = screen.getAllByTestId('WarningGlyphIcon');
        fireEvent.click(warningGlyphIcons[1]);

        const retryButton = screen.getByText('Retry Generation');
        fireEvent.click(retryButton);

        await waitFor(() => {
            expect(resetAbacusStateRequestSpy).toHaveBeenCalled();
            expect(createAbacusEventRequestSpy).toHaveBeenCalled();
            expect(
                screen.getByText(
                    'Regeneration for batch 261 has been started and it will take time.'
                )
            ).toBeDefined();
            expect(props.setRetryBatch).toHaveBeenCalled();
        });
    });

    it('disables batch id links which are in generate, failed and no records state', async () => {
        render();
        expect(screen.getAllByTestId('disabled-batchId-link').length).toEqual(
            3
        );
    });

    it('renders batch id link for applied batch', () => {
        render();
        const link = screen.getByTestId('batchPageLink262') as HTMLLinkElement;
        expect(link.href).toContain(`/adjustments/262`);
    });
});
