import React from 'react';
import { ApolloError } from '@apollo/client';
import { fireEvent, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { renderInAppContext } from '@theorchard/suite-testing';
import {
    mockUseContractTerm,
    mockUseStoreList,
    mockUseTransactionTypeGroups,
    mockUseTransactionTypes,
    mockUseVendorSearch,
} from 'src/__fixtures__/graphql/contract-term-hooks';
import { mockProductSearch } from 'src/__fixtures__/graphql/contract-term-products';
import { mockTrackSearch } from 'src/__fixtures__/graphql/contract-term-tracks';
import { mockAnotherVendor } from 'src/__fixtures__/graphql/contract-term-vendor';
import { mockFetchedTermResponse } from 'src/__fixtures__/graphql/contract-with-term-conditions';
import { useProductSearch as actualUseProductSearchMock } from 'src/apollo/queries/product-search';
import { useTrackSearchByLabelIds as actualUseTrackSearchByLabelIdsMock } from 'src/apollo/queries/track-search';
import { ContractTermsDistroFormFields } from 'src/components/contract-terms-refactored/contract-terms-distro-form-fields';
import { CONTRACT_TERMS_DISTRO_TERM_NAME_ERROR_MSG } from 'src/constants';

jest.mock('src/apollo/queries/track-search');
jest.mock('src/apollo/queries/product-search');

const mockHistoryPush = jest.fn();
const useParamsMock = jest.fn();
jest.mock('react-router-dom', () => ({
    ...jest.requireActual('react-router-dom'),
    useParams: () => useParamsMock(),
    useHistory: () => ({ push: mockHistoryPush }),
}));

const mockSaveHandler = jest.fn();
const mockSaveConditions = jest.fn();
const mockDeleteConditions = jest.fn();
const mockDeleteContractTerm = jest.fn();

jest.mock('src/hooks/contract-terms/contract-term', () => ({
    useSaveHandler: () => mockSaveHandler,
}));
jest.mock('src/hooks/contract-terms/contract-condition', () => ({
    useSaveConditionsHandler: () => mockSaveConditions,
}));
jest.mock('src/apollo/mutations/contract-term-condition', () => ({
    useDeleteContractTermConditions: () => mockDeleteConditions,
}));
jest.mock('src/apollo/mutations/contract-term', () => ({
    useSoftDeleteContractTerm: () => mockDeleteContractTerm,
}));

jest.mock('src/urls/frontend-royalties', () => ({
    getContractDetail: (id: string) => `/contract/${id}`,
}));

const render = (props = {}) =>
    renderInAppContext(<ContractTermsDistroFormFields {...props} />);

const initialFormItemWithoutAttachments = {
    contractId: '123',
    contractTermId: undefined,
    vendor: {
        id: { vendorId: 1 },
        name: 'Test Vendor',
    },
    attachments: [],
    contractTermName: 'Term With Attachments',
    termType: 'track',
    attachmentsRelations: {},
    conditions: [
        {
            contractTermConditionId: '',
            contractTermConditionName: null,
            contractTermId: '',
            priority: 1,
            termRate: '10.00',
            conditions: {
                countries: [],
                stores: [],
                transactionTypes: [],
            },
        },
    ],
    newConditions: [],
};

describe('<ContractTermsDistroFormFields />', () => {
    beforeEach(() => {
        jest.resetAllMocks();

        mockUseContractTerm();

        mockSaveHandler.mockResolvedValue('new-term-id');
        mockSaveConditions.mockResolvedValue({});
        mockDeleteConditions.mockResolvedValue({});
        mockDeleteContractTerm.mockResolvedValue({});
        mockUseStoreList();
        mockUseTransactionTypes();
        mockUseTransactionTypeGroups();
        mockUseVendorSearch();

        (actualUseTrackSearchByLabelIdsMock as jest.Mock).mockReturnValue(
            jest.fn().mockResolvedValue(mockTrackSearch)
        );
        (actualUseProductSearchMock as jest.Mock).mockReturnValue(
            jest.fn().mockResolvedValue(mockProductSearch)
        );
    });

    describe('Initial Rendering and Basic Display', () => {
        it('renders "Edit Track Term" if contractTermId is present', async () => {
            useParamsMock.mockReturnValue({
                contractId: '123',
                termType: 'track',
                contractTermId: '456',
            });
            mockUseContractTerm(mockFetchedTermResponse);

            render();

            const editTerms = await screen.findAllByText('Edit Track Term');
            expect(editTerms).toHaveLength(2);
        });

        it('renders attachments select for track term types', async () => {
            useParamsMock.mockReturnValue({
                contractId: '123',
                termType: 'track',
            });

            render();

            expect(
                await screen.findByTestId(
                    'ContractTermsDistroAttachmentsSelect'
                )
            ).toBeInTheDocument();
        });

        it('renders product attachments when termType is "product"', async () => {
            useParamsMock.mockReturnValue({
                contractId: '123',
                termType: 'product',
            });
            render();

            expect(
                await screen.findByTestId(
                    'ContractTermsDistroAttachmentsSelect'
                )
            ).toBeInTheDocument();
        });

        it('navigates to contract detail on Cancel button click', () => {
            useParamsMock.mockReturnValue({
                contractId: '123',
                termType: 'track',
            });
            render();

            fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));

            expect(mockHistoryPush).toHaveBeenCalledWith('/contract/123');
        });
    });

    describe('Form Validation', () => {
        beforeEach(() => {
            useParamsMock.mockReturnValue({
                contractId: '123',
                termType: 'track',
            });
            render();
        });

        it('shows validation error if contractTermName is empty on blur', async () => {
            const input = screen.getByTestId('contract-term-name-input');
            fireEvent.change(input, { target: { value: '' } });
            fireEvent.blur(input);

            await waitFor(() => {
                expect(
                    screen.getByTestId('contract-term-name-field').innerHTML
                ).toContain(CONTRACT_TERMS_DISTRO_TERM_NAME_ERROR_MSG);
            });
        });

        it('clears validation error when contractTermName becomes valid', async () => {
            const input = screen.getByTestId('contract-term-name-input');
            fireEvent.change(input, { target: { value: '' } });
            fireEvent.blur(input);
            expect(
                screen.getByTestId('contract-term-name-field').innerHTML
            ).toContain(CONTRACT_TERMS_DISTRO_TERM_NAME_ERROR_MSG);

            fireEvent.change(input, { target: { value: 'Valid Name' } });
            fireEvent.blur(input);

            expect(
                screen.queryByText(CONTRACT_TERMS_DISTRO_TERM_NAME_ERROR_MSG, {
                    selector: '.FormControl-error-message',
                })
            ).not.toBeInTheDocument();
        });

        it('prevents save and shows validation error if name is empty on save attempt', async () => {
            const input = screen.getByTestId('contract-term-name-input');
            fireEvent.change(input, { target: { value: '' } });
            fireEvent.blur(input);

            const saveBtn = screen.getByTestId('contractFormFieldsSaveButton');
            fireEvent.click(saveBtn);

            expect(
                screen.getByTestId('contract-term-name-field').innerHTML
            ).toContain(CONTRACT_TERMS_DISTRO_TERM_NAME_ERROR_MSG);

            expect(mockSaveHandler).not.toHaveBeenCalled();
        });
    });

    describe('Saving New Contract Terms', () => {
        beforeEach(() => {
            useParamsMock.mockReturnValue({
                contractId: '123',
                termType: 'track',
            });
            mockUseContractTerm();
        });

        it('saves immediately when attachments exist (without confirmation modal)', async () => {
            (
                actualUseTrackSearchByLabelIdsMock as jest.Mock
            ).mockReturnValueOnce(jest.fn().mockResolvedValue(mockTrackSearch));

            const initialFormItem = {
                contractId: '123',
                contractTermId: undefined,
                vendor: {
                    id: { vendorId: 1 },
                    name: 'Test Vendor',
                },
                attachments: [{ value: 'ISRC123', name: 'ISRC123' }],
                contractTermName: 'Term With Attachments',
                termType: 'track',
                attachmentsRelations: {
                    labelIds: ['1'],
                    upcs: null,
                },
                conditions: initialFormItemWithoutAttachments.conditions,
                newConditions: [],
            };

            render({ contractTermFormItem: initialFormItem });

            const saveBtn = await screen.findByTestId(
                'contractFormFieldsSaveButton'
            );

            await waitFor(() => expect(saveBtn).not.toBeDisabled());

            fireEvent.click(saveBtn);

            await waitFor(() => {
                expect(mockSaveHandler).toHaveBeenCalled();
            });
            expect(mockHistoryPush).toHaveBeenCalledWith('/contract/123');
            await waitFor(() => {
                expect(
                    screen.getByText(
                        'The term has been successfully added to the contract.'
                    )
                ).toBeInTheDocument();
            });
        });

        it('renders server error when save fails', async () => {
            mockSaveHandler.mockRejectedValue(
                new ApolloError({ errorMessage: 'Server Error' })
            );

            const initialFormItem = {
                ...initialFormItemWithoutAttachments,
                attachments: [{ value: 'ISRC123', name: 'ISRC123' }],
                attachmentsRelations: {
                    labelIds: ['1'],
                    upcs: null,
                },
            };

            render({ contractTermFormItem: initialFormItem });

            const saveBtn = await screen.findByTestId(
                'contractFormFieldsSaveButton'
            );

            await waitFor(() => expect(saveBtn).not.toBeDisabled());
            fireEvent.click(saveBtn);
            await waitFor(() => {
                expect(mockSaveHandler).toHaveBeenCalled();
            });

            await waitFor(() => {
                expect(
                    screen.getByText(
                        /Error: Server Error Failed to create\/update contract term\./
                    )
                ).toBeInTheDocument();
            });
        });

        it('opens confirmation modal when saving without attachments and confirms', async () => {
            (
                actualUseTrackSearchByLabelIdsMock as jest.Mock
            ).mockReturnValueOnce(jest.fn().mockResolvedValue([]));

            render({ contractTermFormItem: initialFormItemWithoutAttachments });
            const input = screen.getByTestId('contract-term-name-input');
            fireEvent.change(input, {
                target: { value: 'Term Without Attachments' },
            });
            fireEvent.blur(input);

            const saveBtn = screen.getByTestId('contractFormFieldsSaveButton');
            await waitFor(() => expect(saveBtn).not.toBeDisabled());
            fireEvent.click(saveBtn);

            const modal = await screen.findByRole('dialog');
            expect(modal).toBeInTheDocument();

            const confirm = within(modal).getByText('Save');
            fireEvent.click(confirm);

            await waitFor(() => {
                expect(mockSaveHandler).toHaveBeenCalled();
            });
            await waitFor(() => {
                expect(
                    screen.getByText(
                        'The term has been successfully added to the contract.'
                    )
                ).toBeInTheDocument();
            });
            expect(mockHistoryPush).toHaveBeenCalled();
        });

        it('dismisses confirmation modal and does not save if cancelled', async () => {
            (
                actualUseTrackSearchByLabelIdsMock as jest.Mock
            ).mockReturnValueOnce(jest.fn().mockResolvedValue([]));

            render({ contractTermFormItem: initialFormItemWithoutAttachments });
            const input = screen.getByTestId('contract-term-name-input');
            fireEvent.change(input, { target: { value: 'Unsaved Term' } });
            fireEvent.blur(input);

            const saveBtn = screen.getByTestId('contractFormFieldsSaveButton');
            await waitFor(() => expect(saveBtn).not.toBeDisabled());
            fireEvent.click(saveBtn);

            const modal = await screen.findByRole('dialog');
            expect(modal).toBeInTheDocument();

            const closeButton = modal.querySelector(
                '.ReactModal-close'
            ) as HTMLButtonElement;
            fireEvent.click(closeButton);

            await waitFor(() => {
                expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
            });

            expect(mockSaveHandler).not.toHaveBeenCalled();
            expect(mockHistoryPush).not.toHaveBeenCalled();
        });
    });

    describe('Saving Existing Contract Terms and Conditions', () => {
        beforeEach(() => {
            useParamsMock.mockReturnValue({
                contractId: '123',
                termType: 'track',
                contractTermId: '456',
            });
            mockUseContractTerm(mockFetchedTermResponse);
        });

        it('calls delete handler for removed conditions on save', async () => {
            const conditionToDeleteId = '12345678910';

            const customFetchedTerm = {
                ...mockFetchedTermResponse,
                abacusContractTerm: {
                    ...mockFetchedTermResponse.abacusContractTerm,
                    conditions: [
                        {
                            contractTermConditionId: conditionToDeleteId,
                            contractTermId: '456',
                            priority: 1,
                            termRate: 0.1,
                            contractTermConditionName: 'Old Condition',
                            conditions: {
                                countries: [],
                                stores: [],
                                transactionTypes: [],
                            },
                        },
                        {
                            contractTermConditionId: 'another-condition-id',
                            contractTermId: '456',
                            priority: 2,
                            termRate: 0.2,
                            contractTermConditionName: 'Another Condition',
                            conditions: {
                                countries: [],
                                stores: [],
                                transactionTypes: [],
                            },
                        },
                    ],
                },
            } as any;

            mockUseContractTerm(customFetchedTerm, false, undefined);

            render();

            await screen.findAllByText('Edit Track Term');

            const input = screen.getByTestId('contract-term-name-input');
            fireEvent.change(input, { target: { value: 'New Term Name' } });
            fireEvent.blur(input);

            const conditionRows = screen.getAllByTestId(
                'contractTermConditionRow'
            );
            expect(conditionRows).toHaveLength(2);

            const deleteButton = within(conditionRows[0]).getByTestId(
                'contractTermConditionDeleteIcon'
            );
            fireEvent.click(deleteButton);

            expect(screen.queryByText('Old Condition')).not.toBeInTheDocument();
            expect(
                screen.getAllByTestId('contractTermConditionRow')
            ).toHaveLength(1);

            const saveBtn = screen.getByTestId('contractFormFieldsSaveButton');

            await waitFor(() => expect(saveBtn).not.toBeDisabled(), {
                timeout: 5000,
            });
            fireEvent.click(saveBtn);

            await waitFor(() => {
                expect(mockDeleteConditions).toHaveBeenCalledWith({
                    variables: {
                        contractTermConditionId: [conditionToDeleteId],
                    },
                });
            });

            await waitFor(() => {
                expect(mockSaveHandler).toHaveBeenCalled();
            });
            await waitFor(() => {
                expect(
                    screen.getByText('The term has been successfully updated.')
                ).toBeInTheDocument();
            });
        });

        it('calls saveConditions when conditions exist and term is saved', async () => {
            useParamsMock.mockReturnValue({
                contractId: '123',
                termType: 'track',
            });

            const initialFormItem = {
                contractId: '123',
                contractTermId: undefined,
                vendor: undefined,
                attachments: [{ value: 'ISRC456', name: 'ISRC456' }],
                contractTermName: 'Term With Conditions',
                termType: 'track',
                attachmentsRelations: null,
                conditions: [
                    {
                        contractTermConditionId: undefined,
                        priority: 1,
                        termRate: '0.25',
                        contractTermConditionName: 'Test Condition',
                        conditions: {
                            countries: ['US'],
                            stores: ['Spotify'],
                            transactionTypes: ['CL'],
                        },
                    },
                ],
                newConditions: [],
            };

            render({ contractTermFormItem: initialFormItem });

            const saveBtn = screen.getByTestId('contractFormFieldsSaveButton');
            await waitFor(() => expect(saveBtn).not.toBeDisabled(), {
                timeout: 5000,
            });
            fireEvent.click(saveBtn);

            await waitFor(() => {
                expect(mockSaveHandler).toHaveBeenCalled();
            });

            await waitFor(() => {
                expect(mockSaveConditions).toHaveBeenCalled();
            });
        });
    });

    describe('Deleting Contract Terms', () => {
        beforeEach(() => {
            useParamsMock.mockReturnValue({
                contractId: '123',
                termType: 'track',
                contractTermId: '456',
            });
            mockUseContractTerm(mockFetchedTermResponse);
        });

        it('displays delete term button for existing terms', async () => {
            render();
            const deleteButton = screen.getByTestId(
                'contractFormFieldsDeleteButton'
            );
            expect(deleteButton).toBeInTheDocument();
            expect(deleteButton).toHaveTextContent('Delete Term');
        });

        it('calls delete handler when delete term button is clicked and confirmed', async () => {
            render();

            const deleteButton = screen.getByTestId(
                'contractFormFieldsDeleteButton'
            );
            fireEvent.click(deleteButton);

            const modal = await screen.findByRole('dialog');
            expect(modal).toBeInTheDocument();

            const confirmButton = within(modal).getByText('Delete');
            fireEvent.click(confirmButton);

            await waitFor(() => {
                expect(mockDeleteContractTerm).toHaveBeenCalled();
            });
            await waitFor(() => {
                expect(
                    screen.getByText('The term has been successfully deleted.')
                ).toBeInTheDocument();
            });
            expect(mockHistoryPush).toHaveBeenCalledWith('/contract/123');
        });

        it('does not call delete handler if delete is cancelled in the modal', async () => {
            render();
            const deleteButton = screen.getByTestId(
                'contractFormFieldsDeleteButton'
            );
            fireEvent.click(deleteButton);
            const modal = await screen.findByRole('dialog');
            expect(modal).toBeInTheDocument();

            const cancelButton = within(modal).getByText('cancel');
            fireEvent.click(cancelButton);

            await waitFor(() => {
                expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
            });

            expect(mockDeleteContractTerm).not.toHaveBeenCalled();
            expect(mockHistoryPush).not.toHaveBeenCalled();
        });

        it('does not show delete term button for new terms', () => {
            useParamsMock.mockReturnValue({
                contractId: '123',
                termType: 'track',
            });
            render();
            const deleteButton = screen.queryByTestId(
                'contractFormFieldsDeleteButton'
            );
            expect(deleteButton).not.toBeInTheDocument();
        });
    });

    describe('Vendor Change Behavior', () => {
        it('deletes all attachments when vendor is changed and confirmed', async () => {
            useParamsMock.mockReturnValue({
                contractId: '123',
                termType: 'track',
            });

            const initialFormItem = {
                contractId: '123',
                contractTermId: undefined,
                vendor: {
                    id: { vendorId: 1 },
                    name: 'Original Vendor',
                },
                attachments: [{ value: 'ISRC789', name: 'ISRC789' }],
                contractTermName: 'Term With Vendor',
                termType: 'track',
                attachmentsRelations: {
                    labelIds: ['1'],
                    upcs: null,
                },
                conditions: initialFormItemWithoutAttachments.conditions,
                newConditions: [],
            };

            mockUseVendorSearch([mockAnotherVendor]);

            render({ contractTermFormItem: initialFormItem });

            const user = userEvent.setup();

            const suiteSelect = screen.getByTestId('SuiteSelect');
            const trigger = suiteSelect.querySelector('[aria-haspopup="true"]');
            expect(trigger).toBeTruthy();
            fireEvent.click(trigger!);

            const searchInput = await screen.findByTestId(
                'SuiteListViewFilterInput'
            );
            fireEvent.change(searchInput, {
                target: { value: mockAnotherVendor.name },
            });

            const option = await screen.findByText(mockAnotherVendor.name);
            fireEvent.click(option);

            await screen.findByText('Another Vendor Inc.');

            const saveBtn = screen.getByTestId('contractFormFieldsSaveButton');
            await waitFor(() => expect(saveBtn).not.toBeDisabled());

            await user.click(saveBtn);

            const modal = await screen.findByRole('dialog');
            expect(modal).toBeInTheDocument();

            const confirm = within(modal).getByText('Save');
            fireEvent.click(confirm);
            await waitFor(() =>
                expect(mockSaveHandler).toHaveBeenCalledWith(
                    expect.objectContaining({
                        contractTerm: expect.objectContaining({
                            attachments: [],
                        }),
                    })
                )
            );
            expect(mockHistoryPush).toHaveBeenCalled();
        });
    });
});
