import { render } from '@testing-library/react';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { describe, expect, it, vi } from 'vitest';
import { QuickEditConfig, QuickEditRegistration } from '../Explorer.model';
import { QuickEditContext, QuickEditContextType } from './QuickEditContext';
import {
  useQuickEdit,
  useQuickEditProps,
  useQuickEditReturnType,
} from './useQuickEdit';

const getTestWrapper = (
  props: useQuickEditProps<any>,
): {
  TestWrapper: React.FC<{
    quickEditValuesRef: React.MutableRefObject<useQuickEditReturnType<any>>;
  }>;
  ref: React.MutableRefObject<useQuickEditReturnType<any>>;
} => {
  const ref = { current: {} as useQuickEditReturnType<any> };

  const TestWrapper: React.FC<{
    quickEditValuesRef: React.MutableRefObject<useQuickEditReturnType<any>>;
  }> = ({ quickEditValuesRef, children }) => {
    quickEditValuesRef.current = useQuickEdit(props);

    return <>{children}</>;
  };

  return { TestWrapper, ref };
};

const getContextConsumer = () => {
  const ref = { current: {} as QuickEditContextType<any> };

  const ContextConsumer: React.FC<{
    contextRef: React.MutableRefObject<QuickEditContextType<any>>;
  }> = ({ contextRef }) => {
    contextRef.current = React.useContext(QuickEditContext);
    return <></>;
  };

  return { ContextConsumer, ref };
};

const mockListRef = {
  current: { resetSelection: vi.fn(), selectIndex: vi.fn() },
};

const mockRegistrations: QuickEditRegistration<unknown>[] = [
  {
    label: 'Test 1',
    generateDetailsLink: () => `/test`,
    component: vi.fn() as any as JSX.Element,
  },
  {
    label: 'Test 2',
    generateDetailsLink: (item) => `/test?${JSON.stringify(item)}`,
    component: vi.fn() as any as JSX.Element,
  },
  {
    label: 'Test 3',
    component: vi.fn() as any as JSX.Element,
  },

  {
    label: 'Test 4',
    generateDetailsLink: false,
    component: vi.fn() as any as JSX.Element,
  },
];

const defaultProps: useQuickEditProps<any> = {
  listRef: mockListRef,
  registrations: mockRegistrations,
  collapseFilters: vi.fn(),
  hasData: true,
  defaultItem: { id: 0 },
  generateItemLink: vi.fn(),
};

describe('useQuickEdit', () => {
  it('should return the quickEdit action if registrations are defined', () => {
    const { TestWrapper, ref } = getTestWrapper(defaultProps);

    render(<TestWrapper quickEditValuesRef={ref} />);

    expect(ref.current.quickEditAction).toBeDefined();
  });

  it('should not return the quickEdit action if registrations are not defined', () => {
    const { TestWrapper, ref } = getTestWrapper({
      ...defaultProps,
      registrations: undefined,
    });

    render(<TestWrapper quickEditValuesRef={ref} />);

    expect(ref.current.quickEditAction).toBeUndefined();
  });

  it('should generate actions for each registration', () => {
    const { TestWrapper, ref } = getTestWrapper(defaultProps);

    render(<TestWrapper quickEditValuesRef={ref} />);

    expect(ref.current.quickEditAction?.kind).toBe('group');
    expect(
      ref.current.quickEditAction?.kind === 'group' &&
        ref.current.quickEditAction.actions,
    ).toHaveLength(mockRegistrations.length);
  });

  it('should set isQuickEditMode to true and collapse filters when quickEditAction is opened', async () => {
    const collapseFiltersSpy = vi.fn();
    const { TestWrapper, ref } = getTestWrapper({
      ...defaultProps,
      collapseFilters: collapseFiltersSpy,
    });

    render(<TestWrapper quickEditValuesRef={ref} />);

    await act(async () => {
      ref.current.quickEditAction?.kind === 'group' &&
        (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
    });

    expect(ref.current.isQuickEditMode).toBe(true);
    expect(collapseFiltersSpy).toHaveBeenCalled();
  });

  it('should set the first registration as the current registration when quickEditAction is clicked', async () => {
    const { TestWrapper, ref } = getTestWrapper(defaultProps);

    render(<TestWrapper quickEditValuesRef={ref} />);

    await act(async () => {
      ref.current.quickEditAction?.kind === 'group' &&
        (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
    });

    expect(ref.current.quickEditStation).toBe(mockRegistrations[0].component);
  });

  it('should update the registration when a registration action is clicked', async () => {
    const { TestWrapper, ref } = getTestWrapper(defaultProps);

    render(<TestWrapper quickEditValuesRef={ref} />);

    await act(async () => {
      ref.current.quickEditAction?.kind === 'group' &&
        (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
    });

    await act(async () => {
      ref.current.quickEditAction?.kind === 'group' &&
        ref.current.quickEditAction.actions[1].onClick?.();
    });

    expect(ref.current.quickEditStation).toBe(mockRegistrations[1].component);
  });

  it('should disable the quickEditAction when there is no data', () => {
    const { TestWrapper, ref } = getTestWrapper({
      ...defaultProps,
      hasData: false,
    });

    render(<TestWrapper quickEditValuesRef={ref} />);

    expect(
      ref.current.quickEditAction?.kind === 'group' &&
        ref.current.quickEditAction.disabled,
    ).toBe(true);
  });

  it('should use "Quick View" as the default label when no quickEditConfig is provided', () => {
    const { TestWrapper, ref } = getTestWrapper(defaultProps);

    render(<TestWrapper quickEditValuesRef={ref} />);

    expect(
      ref.current.quickEditAction?.kind === 'group' &&
        ref.current.quickEditAction.label,
    ).toBe('Quick View');
  });

  it('should use the label from quickEditConfig when provided', () => {
    const quickEditConfig: QuickEditConfig = { label: 'Custom Label' };
    const { TestWrapper, ref } = getTestWrapper({
      ...defaultProps,
      quickEditConfig,
    });

    render(<TestWrapper quickEditValuesRef={ref} />);

    expect(
      ref.current.quickEditAction?.kind === 'group' &&
        ref.current.quickEditAction.label,
    ).toBe('Custom Label');
  });

  describe('List Actions', () => {
    it('should select the first item when quick edit is opened', async () => {
      const { TestWrapper, ref } = getTestWrapper(defaultProps);

      render(<TestWrapper quickEditValuesRef={ref} />);

      await act(async () => {
        ref.current.quickEditAction?.kind === 'group' &&
          (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
      });

      expect(mockListRef.current.selectIndex).toHaveBeenCalledWith(0);
    });

    it('should reset the selection when quick edit is closed', async () => {
      const { TestWrapper, ref } = getTestWrapper(defaultProps);

      render(<TestWrapper quickEditValuesRef={ref} />);

      await act(async () => {
        ref.current.quickEditAction?.kind === 'group' &&
          (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
      });

      await act(async () => {
        ref.current.quickEditAction?.kind === 'group' &&
          (await ref.current.quickEditAction?.onActionsGroupToggled?.(false));
      });

      expect(mockListRef.current.resetSelection).toHaveBeenCalled();
    });
  });

  describe('QuickEditContextProvider', () => {
    it('should select the default item when quick edit is opened', async () => {
      const { TestWrapper, ref } = getTestWrapper(defaultProps);

      render(<TestWrapper quickEditValuesRef={ref} />);

      await act(async () => {
        ref.current.quickEditAction?.kind === 'group' &&
          (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
      });

      const { ContextConsumer, ref: contextRef } = getContextConsumer();
      render(
        <ref.current.QuickEditContextProvider>
          <ContextConsumer contextRef={contextRef} />
        </ref.current.QuickEditContextProvider>,
      );

      expect(contextRef.current.isQuickEditMode).toBe(true);
      expect(contextRef.current.selectedItem).toEqual({ id: 0 });
      expect(contextRef.current.detailsLink).toBe('/test');
    });

    it('should provide the correct context values', async () => {
      const { TestWrapper, ref } = getTestWrapper(defaultProps);

      render(<TestWrapper quickEditValuesRef={ref} />);

      await act(async () => {
        ref.current.quickEditAction?.kind === 'group' &&
          (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
        ref.current.changeSelectedItem({ id: 1 });
      });

      const { ContextConsumer, ref: contextRef } = getContextConsumer();
      render(
        <ref.current.QuickEditContextProvider>
          <ContextConsumer contextRef={contextRef} />
        </ref.current.QuickEditContextProvider>,
      );

      expect(contextRef.current.isQuickEditMode).toBe(true);
      expect(contextRef.current.selectedItem).toEqual({ id: 1 });
      expect(contextRef.current.detailsLink).toBe('/test');
    });

    it('should call the save callback when changeSelectedItem is called', async () => {
      const { TestWrapper, ref } = getTestWrapper(defaultProps);

      render(<TestWrapper quickEditValuesRef={ref} />);

      await act(async () => {
        ref.current.quickEditAction?.kind === 'group' &&
          (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
        ref.current.changeSelectedItem({ id: 1 });
      });

      const { ContextConsumer, ref: contextRef } = getContextConsumer();
      render(
        <ref.current.QuickEditContextProvider>
          <ContextConsumer contextRef={contextRef} />
        </ref.current.QuickEditContextProvider>,
      );

      const saveCallbackSpy = vi.fn();

      await act(async () => {
        contextRef.current.registerSaveCallback?.(saveCallbackSpy);
        ref.current.changeSelectedItem({ id: 1 });
      });

      expect(saveCallbackSpy).toHaveBeenCalled();
    });

    it('should call the save callback when refresh is called', async () => {
      const { TestWrapper, ref } = getTestWrapper(defaultProps);

      render(<TestWrapper quickEditValuesRef={ref} />);

      await act(async () => {
        ref.current.quickEditAction?.kind === 'group' &&
          (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
        ref.current.changeSelectedItem({ id: 1 });
      });

      const { ContextConsumer, ref: contextRef } = getContextConsumer();
      render(
        <ref.current.QuickEditContextProvider>
          <ContextConsumer contextRef={contextRef} />
        </ref.current.QuickEditContextProvider>,
      );

      const saveCallbackSpy = vi.fn();

      await act(async () => {
        contextRef.current.registerSaveCallback?.(saveCallbackSpy);
        contextRef.current.refresh?.();
      });

      expect(saveCallbackSpy).toHaveBeenCalled();
    });

    it('should generate the correct details link using registration if provided', async () => {
      const { TestWrapper, ref } = getTestWrapper({
        ...defaultProps,
        registrations: [mockRegistrations[1]],
      });

      render(<TestWrapper quickEditValuesRef={ref} />);

      await act(async () => {
        ref.current.quickEditAction?.kind === 'group' &&
          (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));

        ref.current.changeSelectedItem({ id: 1 });
      });

      const { ContextConsumer, ref: contextRef } = getContextConsumer();
      render(
        <ref.current.QuickEditContextProvider>
          <ContextConsumer contextRef={contextRef} />
        </ref.current.QuickEditContextProvider>,
      );

      expect(contextRef.current.detailsLink).toBe('/test?{"id":1}');
    });

    it('should generate the correct details link using generateItemLink prop if not provided in registration', async () => {
      const generateDetailsLinkSpy = vi.fn();

      const { TestWrapper, ref } = getTestWrapper({
        ...defaultProps,
        registrations: [mockRegistrations[2]],
        generateItemLink: generateDetailsLinkSpy,
      });

      render(<TestWrapper quickEditValuesRef={ref} />);

      await act(async () => {
        ref.current.quickEditAction?.kind === 'group' &&
          (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));

        ref.current.changeSelectedItem({ id: 1 });
      });

      const { ContextConsumer, ref: contextRef } = getContextConsumer();

      render(
        <ref.current.QuickEditContextProvider>
          <ContextConsumer contextRef={contextRef} />
        </ref.current.QuickEditContextProvider>,
      );

      expect(generateDetailsLinkSpy).toHaveBeenCalledWith({ id: 1 });
    });

    it('should not generate a details link if generateDetailsLink in registration is false', async () => {
      const { TestWrapper, ref } = getTestWrapper({
        ...defaultProps,
        registrations: [mockRegistrations[3]],
      });

      render(<TestWrapper quickEditValuesRef={ref} />);

      await act(async () => {
        ref.current.quickEditAction?.kind === 'group' &&
          (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));

        ref.current.changeSelectedItem({ id: 1 });
      });

      const { ContextConsumer, ref: contextRef } = getContextConsumer();
      render(
        <ref.current.QuickEditContextProvider>
          <ContextConsumer contextRef={contextRef} />
        </ref.current.QuickEditContextProvider>,
      );

      expect(contextRef.current.detailsLink).toBeUndefined();
    });
  });
});
