import fetchMock from 'fetch-mock';
import {
  SupersetClientClass,
  SupersetClient,
  buildQueryContext,
  QueryFormData,
  configure as configureTranslation,
} from '@superset-ui/core/src';

import {
  ChartClient,
  getChartBuildQueryRegistry,
  getChartMetadataRegistry,
  ChartMetadata,
} from '@superset-ui/core/src/chart';
import { SliceIdAndOrFormData } from '@superset-ui/core/src/chart/clients/ChartClient';
import { LOGIN_GLOB } from '../fixtures/constants';
import { sankeyFormData } from '../fixtures/formData';

configureTranslation();

describe('ChartClient', () => {
  let chartClient: ChartClient;

  beforeAll(() => {
    fetchMock.get(LOGIN_GLOB, { result: '1234' });
    SupersetClient.reset();
    SupersetClient.configure().init();
  });

  beforeEach(() => {
    chartClient = new ChartClient();
  });

  afterEach(fetchMock.restore);

  describe('new ChartClient(config)', () => {
    it('creates a client without argument', () => {
      expect(chartClient).toBeInstanceOf(ChartClient);
    });
    it('creates a client with specified config.client', () => {
      const customClient = new SupersetClientClass();
      chartClient = new ChartClient({ client: customClient });
      expect(chartClient).toBeInstanceOf(ChartClient);
      expect(chartClient.client).toBe(customClient);
    });
  });

  describe('.loadFormData({ sliceId, formData }, options)', () => {
    const sliceId = 123;
    it('fetches formData if given only sliceId', () => {
      fetchMock.get(`glob:*/api/v1/form_data/?slice_id=${sliceId}`, sankeyFormData);

      return expect(chartClient.loadFormData({ sliceId })).resolves.toEqual(sankeyFormData);
    });
    it('fetches formData from sliceId and merges with specify formData if both fields are specified', () => {
      fetchMock.get(`glob:*/api/v1/form_data/?slice_id=${sliceId}`, sankeyFormData);

      return expect(
        chartClient.loadFormData({
          sliceId,
          formData: {
            granularity: 'second',
            viz_type: 'bar',
          },
        }),
      ).resolves.toEqual({
        ...sankeyFormData,
        granularity: 'second',
        viz_type: 'bar',
      });
    });
    it('returns promise of formData if only formData was given', () =>
      expect(
        chartClient.loadFormData({
          formData: {
            datasource: '1__table',
            granularity: 'minute',
            viz_type: 'line',
          },
        }),
      ).resolves.toEqual({
        datasource: '1__table',
        granularity: 'minute',
        viz_type: 'line',
      }));
    it('rejects if none of sliceId or formData is specified', () =>
      expect(chartClient.loadFormData({} as SliceIdAndOrFormData)).rejects.toEqual(
        new Error('At least one of sliceId or formData must be specified'),
      ));
  });

  describe('.loadQueryData(formData, options)', () => {
    it('returns a promise of query data for known chart type', () => {
      getChartMetadataRegistry().registerValue(
        'word_cloud',
        new ChartMetadata({ name: 'Word Cloud', thumbnail: '' }),
      );

      getChartBuildQueryRegistry().registerValue('word_cloud', (formData: QueryFormData) =>
        buildQueryContext(formData),
      );
      fetchMock.post('glob:*/api/v1/chart/data', [
        {
          field1: 'abc',
          field2: 'def',
        },
      ]);

      return expect(
        chartClient.loadQueryData({
          granularity: 'minute',
          viz_type: 'word_cloud',
          datasource: '1__table',
        }),
      ).resolves.toEqual([
        {
          field1: 'abc',
          field2: 'def',
        },
      ]);
    });
    it('returns a promise that rejects for unknown chart type', () =>
      expect(
        chartClient.loadQueryData({
          granularity: 'minute',
          viz_type: 'rainbow_3d_pie',
          datasource: '1__table',
        }),
      ).rejects.toEqual(new Error('Unknown chart type: rainbow_3d_pie')));

    it('fetches data from the legacy API if ChartMetadata has useLegacyApi=true,', () => {
      // note legacy charts do not register a buildQuery function in the registry
      getChartMetadataRegistry().registerValue(
        'word_cloud_legacy',
        new ChartMetadata({
          name: 'Legacy Word Cloud',
          thumbnail: '.png',
          useLegacyApi: true,
        }),
      );

      fetchMock.post('glob:*/api/v1/chart/data', () =>
        Promise.reject(new Error('Unexpected all to v1 API')),
      );

      fetchMock.post('glob:*/superset/explore_json/', {
        field1: 'abc',
        field2: 'def',
      });

      return expect(
        chartClient.loadQueryData({
          granularity: 'minute',
          viz_type: 'word_cloud_legacy',
          datasource: '1__table',
        }),
      ).resolves.toEqual([
        {
          field1: 'abc',
          field2: 'def',
        },
      ]);
    });
  });

  describe('.loadDatasource(datasourceKey, options)', () => {
    it('fetches datasource', () => {
      fetchMock.get('glob:*/superset/fetch_datasource_metadata?datasourceKey=1__table', {
        field1: 'abc',
        field2: 'def',
      });

      return expect(chartClient.loadDatasource('1__table')).resolves.toEqual({
        field1: 'abc',
        field2: 'def',
      });
    });
  });

  describe('.loadAnnotation(annotationLayer)', () => {
    it('returns an empty object if the annotation layer does not require query', () =>
      expect(
        chartClient.loadAnnotation({
          name: 'my-annotation',
        }),
      ).resolves.toEqual({}));
    it('otherwise returns a rejected promise because it is not implemented yet', () =>
      expect(
        chartClient.loadAnnotation({
          name: 'my-annotation',
          sourceType: 'abc',
        }),
      ).rejects.toEqual(new Error('This feature is not implemented yet.')));
  });

  describe('.loadAnnotations(annotationLayers)', () => {
    it('loads multiple annotation layers and combine results', () =>
      expect(
        chartClient.loadAnnotations([
          {
            name: 'anno1',
          },
          {
            name: 'anno2',
          },
          {
            name: 'anno3',
          },
        ]),
      ).resolves.toEqual({
        anno1: {},
        anno2: {},
        anno3: {},
      }));
    it('returns an empty object if input is not an array', () =>
      expect(chartClient.loadAnnotations()).resolves.toEqual({}));
    it('returns an empty object if input is an empty array', () =>
      expect(chartClient.loadAnnotations()).resolves.toEqual({}));
  });

  describe('.loadChartData({ sliceId, formData })', () => {
    const sliceId = 10120;
    it('loadAllDataNecessaryForAChart', () => {
      fetchMock.get(`glob:*/api/v1/form_data/?slice_id=${sliceId}`, {
        granularity: 'minute',
        viz_type: 'line',
        datasource: '1__table',
        color: 'living-coral',
      });

      fetchMock.get('glob:*/superset/fetch_datasource_metadata?datasourceKey=1__table', {
        name: 'transactions',
        schema: 'staging',
      });

      fetchMock.post('glob:*/api/v1/chart/data', {
        lorem: 'ipsum',
        dolor: 'sit',
        amet: true,
      });

      getChartMetadataRegistry().registerValue(
        'line',
        new ChartMetadata({ name: 'Line', thumbnail: '.gif' }),
      );

      getChartBuildQueryRegistry().registerValue('line', (formData: QueryFormData) =>
        buildQueryContext(formData),
      );

      return expect(
        chartClient.loadChartData({
          sliceId,
        }),
      ).resolves.toEqual({
        annotationData: {},
        datasource: {
          name: 'transactions',
          schema: 'staging',
        },
        formData: {
          granularity: 'minute',
          viz_type: 'line',
          datasource: '1__table',
          color: 'living-coral',
        },
        queriesData: [
          {
            lorem: 'ipsum',
            dolor: 'sit',
            amet: true,
          },
        ],
      });
    });
  });
});
