import React from 'react';

import { initializeMockApp } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import MockAdapter from 'axios-mock-adapter';
import {
  getCommitLibraryChangesUrl,
  getCreateLibraryBlockUrl,
  getLibraryCollectionItemsApiUrl,
  getLibraryCollectionsApiUrl,
  getLibraryCollectionApiUrl,
  getBlockTypesMetaDataUrl,
  getLibraryContainerApiUrl,
  getLibraryContainerRestoreApiUrl,
  getLibraryContainerChildrenApiUrl,
  getLibraryContainerPublishApiUrl,
} from './api';
import {
  useCommitLibraryChanges,
  useCreateLibraryBlock,
  useCreateLibraryCollection,
  useRevertLibraryChanges,
  useAddItemsToCollection,
  useCollection,
  useBlockTypesMetadata,
  useContainer,
  useDeleteContainer,
  useRestoreContainer,
  useContainerChildren,
  useAddItemsToContainer,
  useUpdateContainerChildren,
  useRemoveContainerChildren,
  usePublishContainer,
} from './apiHooks';

let axiosMock;

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: false,
    },
  },
});

const wrapper = ({ children }) => (
  <QueryClientProvider client={queryClient}>
    {children}
  </QueryClientProvider>
);

describe('library api hooks', () => {
  beforeEach(() => {
    initializeMockApp({
      authenticatedUser: {
        userId: 3,
        username: 'abc123',
        administrator: true,
        roles: [],
      },
    });
    axiosMock = new MockAdapter(getAuthenticatedHttpClient());
  });

  it('should create library block', async () => {
    const libraryId = 'lib:org:1';
    const url = getCreateLibraryBlockUrl(libraryId);
    axiosMock.onPost(url).reply(200);
    const { result } = renderHook(() => useCreateLibraryBlock(), { wrapper });
    await result.current.mutateAsync({
      libraryId,
      blockType: 'html',
      definitionId: '1',
    });

    expect(axiosMock.history.post[0].url).toEqual(url);
  });

  it('should commit library changes', async () => {
    const libraryId = 'lib:org:1';
    const url = getCommitLibraryChangesUrl(libraryId);
    axiosMock.onPost(url).reply(200);
    const { result } = renderHook(() => useCommitLibraryChanges(), { wrapper });
    await result.current.mutateAsync(libraryId);

    expect(axiosMock.history.post[0].url).toEqual(url);
  });

  it('should revert library changes', async () => {
    const libraryId = 'lib:org:1';
    const url = getCommitLibraryChangesUrl(libraryId);
    axiosMock.onDelete(url).reply(200);
    const { result } = renderHook(() => useRevertLibraryChanges(), { wrapper });
    await result.current.mutateAsync(libraryId);

    expect(axiosMock.history.delete[0].url).toEqual(url);
  });

  it('should create collection', async () => {
    const libraryId = 'lib:org:1';
    const url = getLibraryCollectionsApiUrl(libraryId);
    axiosMock.onPost(url).reply(200);

    const { result } = renderHook(() => useCreateLibraryCollection(libraryId), { wrapper });
    await result.current.mutateAsync({
      title: 'This is a test',
      description: 'This is only a test',
    });

    expect(axiosMock.history.post[0].url).toEqual(url);
  });

  it('should add components to collection', async () => {
    const libraryId = 'lib:org:1';
    const collectionId = 'my-first-collection';
    const url = getLibraryCollectionItemsApiUrl(libraryId, collectionId);
    axiosMock.onPatch(url).reply(200);
    const { result } = renderHook(() => useAddItemsToCollection(libraryId, collectionId), { wrapper });
    await result.current.mutateAsync(['some-usage-key']);

    expect(axiosMock.history.patch[0].url).toEqual(url);
  });

  it('should get collection metadata', async () => {
    const libraryId = 'lib:org:1';
    const collectionId = 'my-first-collection';
    const url = getLibraryCollectionApiUrl(libraryId, collectionId);

    axiosMock.onGet(url).reply(200, { 'test-data': 'test-value' });
    const { result } = renderHook(() => useCollection(libraryId, collectionId), { wrapper });
    await waitFor(() => {
      expect(result.current.isLoading).toBeFalsy();
    });
    expect(result.current.data).toEqual({ testData: 'test-value' });
    expect(axiosMock.history.get[0].url).toEqual(url);
  });

  it('should get block types metadata', async () => {
    const libraryId = 'lib:org:1';
    const url = getBlockTypesMetaDataUrl(libraryId);

    axiosMock.onGet(url).reply(200, { 'test-data': 'test-value' });
    const { result } = renderHook(() => useBlockTypesMetadata(libraryId), { wrapper });
    await waitFor(() => {
      expect(result.current.isLoading).toBeFalsy();
    });
    expect(result.current.data).toEqual({ testData: 'test-value' });
    expect(axiosMock.history.get[0].url).toEqual(url);
  });

  it('should get container metadata', async () => {
    const containerId = 'lct:lib:org:unit:unit1';
    const url = getLibraryContainerApiUrl(containerId);

    axiosMock.onGet(url).reply(200, { 'test-data': 'test-value' });
    const { result } = renderHook(() => useContainer(containerId), { wrapper });
    await waitFor(() => {
      expect(result.current.isLoading).toBeFalsy();
    });
    expect(result.current.data).toEqual({ testData: 'test-value' });
    expect(axiosMock.history.get[0].url).toEqual(url);
  });

  it('should delete a container', async () => {
    const containerId = 'lct:org:lib:unit:unit1';
    const url = getLibraryContainerApiUrl(containerId);

    axiosMock.onDelete(url).reply(200);
    const { result } = renderHook(() => useDeleteContainer(containerId), { wrapper });
    await result.current.mutateAsync();
    await waitFor(() => {
      expect(axiosMock.history.delete[0].url).toEqual(url);
    });
  });

  it('should restore a container', async () => {
    const containerId = 'lct:org:lib:unit:unit1';
    const url = getLibraryContainerRestoreApiUrl(containerId);

    axiosMock.onPost(url).reply(200);
    const { result } = renderHook(() => useRestoreContainer(containerId), { wrapper });
    await result.current.mutateAsync();
    await waitFor(() => {
      expect(axiosMock.history.post[0].url).toEqual(url);
    });
  });

  it('should get container children', async () => {
    const containerId = 'lct:lib:org:unit:unit1';
    const url = getLibraryContainerChildrenApiUrl(containerId);

    axiosMock.onGet(url).reply(200, [
      {
        id: 'lb:org1:Demo_course:html:text',
        block_type: 'html',
        display_name: 'text block',
        last_published: null,
        published_by: null,
        last_draft_created: null,
        last_draft_created_by: null,
        has_unpublished_changes: false,
        created: null,
        modified: null,
        tags_count: 0,
        collections: ['col1', 'col2'],
      },
      {
        id: 'lb:org1:Demo_course:video:video1',
        block_type: 'video',
        display_name: 'video block',
        last_published: null,
        published_by: null,
        last_draft_created: null,
        last_draft_created_by: null,
        has_unpublished_changes: false,
        created: null,
        modified: null,
        tags_count: 0,
        collections: ['col2'],
      },
    ]);
    const { result } = renderHook(() => useContainerChildren(containerId), { wrapper });
    await waitFor(() => {
      expect(result.current.isLoading).toBeFalsy();
    });
    expect(result.current.data).toEqual([
      {
        id: 'lb:org1:Demo_course:html:text',
        blockType: 'html',
        displayName: 'text block',
        lastPublished: null,
        publishedBy: null,
        lastDraftCreated: null,
        lastDraftCreatedBy: null,
        hasUnpublishedChanges: false,
        created: null,
        modified: null,
        tagsCount: 0,
        collections: ['col1', 'col2'],
      },
      {
        id: 'lb:org1:Demo_course:video:video1',
        blockType: 'video',
        displayName: 'video block',
        lastPublished: null,
        publishedBy: null,
        lastDraftCreated: null,
        lastDraftCreatedBy: null,
        hasUnpublishedChanges: false,
        created: null,
        modified: null,
        tagsCount: 0,
        collections: ['col2'],
      },
    ]);
    expect(axiosMock.history.get[0].url).toEqual(url);
  });

  it('should add components to container', async () => {
    const componentId = 'lb:org:lib:html:1';
    const containerId = 'lct:org:lib:unit:1';

    const url = getLibraryContainerChildrenApiUrl(containerId);

    axiosMock.onPost(url).reply(200);
    const { result } = renderHook(() => useAddItemsToContainer(containerId), { wrapper });
    await result.current.mutateAsync([componentId]);

    expect(axiosMock.history.post[0].url).toEqual(url);
  });

  it('should update container children', async () => {
    const containerId = 'lct:org:lib:unit:unit-1';
    const url = getLibraryContainerChildrenApiUrl(containerId);

    axiosMock.onPatch(url).reply(200);
    const { result } = renderHook(() => useUpdateContainerChildren(containerId), { wrapper });
    await result.current.mutateAsync([]);
    await waitFor(() => {
      expect(axiosMock.history.patch[0].url).toEqual(url);
    });
  });

  it('should not attempt request if containerId is not defined', async () => {
    const { result } = renderHook(() => useUpdateContainerChildren(), { wrapper });
    await result.current.mutateAsync([]);
    await waitFor(() => {
      expect(axiosMock.history.patch.length).toEqual(0);
    });
  });

  it('should remove container children', async () => {
    const containerId = 'lct:org:lib:unit:unit-1';
    const url = getLibraryContainerChildrenApiUrl(containerId);

    axiosMock.onDelete(url).reply(200);
    const { result } = renderHook(() => useRemoveContainerChildren(containerId), { wrapper });
    await result.current.mutateAsync([]);
    await waitFor(() => {
      expect(axiosMock.history.delete[0].url).toEqual(url);
    });
  });

  it('should not attempt request if containerId is not defined in remove children from container', async () => {
    const { result } = renderHook(() => useRemoveContainerChildren(), { wrapper });
    await result.current.mutateAsync([]);
    await waitFor(() => {
      expect(axiosMock.history.patch.length).toEqual(0);
    });
  });

  it('should invalidate subsection when added to section', async () => {
    const spy = jest.spyOn(queryClient, 'invalidateQueries');
    const subsectionId1 = 'lct:org:lib:subsection:1';
    const subsectionId2 = 'lct:org:lib:subsection:2';
    const sectionId = 'lct:org:lib:section:1';
    const url = getLibraryContainerChildrenApiUrl(sectionId);

    axiosMock.onPost(url).reply(200);

    const { result } = renderHook(() => useAddItemsToContainer(sectionId), { wrapper });

    await result.current.mutateAsync([subsectionId1, subsectionId2]);

    expect(axiosMock.history.post[0].url).toEqual(url);

    // Keys should be invalidated:
    // 1. library
    // 2. containerChildren
    // 3. container
    // 4. containerHierarchy
    // 5 & 6. subsections
    // 7 all hierarchies
    expect(spy).toHaveBeenCalledTimes(7);
  });

  describe('publishContainer', () => {
    it('should publish a container', async () => {
      const containerId = 'lct:org:lib:unit:1';
      const url = getLibraryContainerPublishApiUrl(containerId);
      axiosMock.onPost(url).reply(200);
      const { result } = renderHook(() => usePublishContainer(containerId), { wrapper });
      await result.current.mutateAsync();

      expect(axiosMock.history.post[0].url).toEqual(url);
    });
  });
});
