import { type DropResult } from '@hello-pangea/dnd';
import { renderHook } from '@testing-library/react';

import { type DataQuery } from '@grafana/schema';

import { type Transformation } from '../../types';

import { useSidebarDragAndDrop } from './useSidebarDragAndDrop';

const mockUpdateQueries = jest.fn();
const mockReorderTransformations = jest.fn();
const mockSetSelectedQuery = jest.fn();
const mockSetSelectedTransformation = jest.fn();

const queries: DataQuery[] = [
  { refId: 'A', datasource: { type: 'prometheus', uid: 'prom1' } },
  { refId: 'B', datasource: { type: 'prometheus', uid: 'prom1' } },
  { refId: 'C', datasource: { type: 'prometheus', uid: 'prom1' } },
];

const transformations: Transformation[] = [
  { transformId: 't1', registryItem: undefined, transformConfig: { id: 'organize', options: {} } },
  { transformId: 't2', registryItem: undefined, transformConfig: { id: 'reduce', options: {} } },
  { transformId: 't3', registryItem: undefined, transformConfig: { id: 'filter', options: {} } },
];

jest.mock('../../QueryEditorContext', () => ({
  useQueryRunnerContext: () => ({
    queries,
  }),
  usePanelContext: () => ({
    transformations,
  }),
  useActionsContext: () => ({
    updateQueries: mockUpdateQueries,
    reorderTransformations: mockReorderTransformations,
  }),
  useQueryEditorUIContext: () => ({
    setSelectedQuery: mockSetSelectedQuery,
    setSelectedTransformation: mockSetSelectedTransformation,
  }),
}));

function makeDropResult(sourceIndex: number, destinationIndex: number | null): DropResult {
  return {
    draggableId: 'test',
    type: 'DEFAULT',
    reason: 'DROP',
    mode: 'FLUID',
    source: { droppableId: 'droppable', index: sourceIndex },
    destination: destinationIndex !== null ? { droppableId: 'droppable', index: destinationIndex } : null,
    combine: null,
  };
}

describe('useSidebarDragAndDrop', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('onQueryDragEnd', () => {
    it('should reorder queries on successful drag', () => {
      const { result } = renderHook(() => useSidebarDragAndDrop());

      result.current.onQueryDragEnd(makeDropResult(0, 2));

      expect(mockUpdateQueries).toHaveBeenCalledWith([queries[1], queries[2], queries[0]]);
    });

    it('should select the dragged query after reorder', () => {
      const { result } = renderHook(() => useSidebarDragAndDrop());

      result.current.onQueryDragEnd(makeDropResult(0, 2));

      expect(mockSetSelectedQuery).toHaveBeenCalledWith(queries[0]);
    });

    it('should not reorder when dropped outside droppable area', () => {
      const { result } = renderHook(() => useSidebarDragAndDrop());

      result.current.onQueryDragEnd(makeDropResult(0, null));

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

    it('should not reorder when dropped at same index', () => {
      const { result } = renderHook(() => useSidebarDragAndDrop());

      result.current.onQueryDragEnd(makeDropResult(1, 1));

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

  describe('onTransformationDragEnd', () => {
    it('should reorder transformations on successful drag', () => {
      const { result } = renderHook(() => useSidebarDragAndDrop());

      result.current.onTransformationDragEnd(makeDropResult(0, 2));

      expect(mockReorderTransformations).toHaveBeenCalledWith([
        transformations[1].transformConfig,
        transformations[2].transformConfig,
        transformations[0].transformConfig,
      ]);
    });

    it('should select the dragged transformation with updated index-based id after reorder', () => {
      const { result } = renderHook(() => useSidebarDragAndDrop());

      result.current.onTransformationDragEnd(makeDropResult(0, 2));

      expect(mockSetSelectedTransformation).toHaveBeenCalledWith({
        ...transformations[0],
        transformId: `${transformations[0].transformConfig.id}-2`,
      });
    });

    it('should not reorder when dropped outside droppable area', () => {
      const { result } = renderHook(() => useSidebarDragAndDrop());

      result.current.onTransformationDragEnd(makeDropResult(0, null));

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

    it('should not reorder when dropped at same index', () => {
      const { result } = renderHook(() => useSidebarDragAndDrop());

      result.current.onTransformationDragEnd(makeDropResult(1, 1));

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