/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import sinon from 'sinon';

import URI from 'urijs';
import {
  buildV1ChartDataPayload,
  getExploreUrl,
  getExploreLongUrl,
  getDataTablePageSize,
  shouldUseLegacyApi,
  getSimpleSQLExpression,
} from 'src/explore/exploreUtils';
import {
  buildTimeRangeString,
  formatTimeRange,
} from 'src/explore/dateFilterUtils';
import * as hostNamesConfig from 'src/utils/hostNamesConfig';
import { getChartMetadataRegistry } from '@superset-ui/core';

describe('exploreUtils', () => {
  const { location } = window;
  const formData = {
    datasource: '1__table',
  };
  const sFormData = JSON.stringify(formData);
  function compareURI(uri1, uri2) {
    expect(uri1.toString()).toBe(uri2.toString());
  }

  describe('getExploreUrl', () => {
    it('generates proper base url', () => {
      // This assertion is to show clearly the value of location.href
      // in the context of unit tests.
      expect(location.href).toBe('http://localhost/');

      const url = getExploreUrl({
        formData,
        endpointType: 'base',
        force: false,
        curUrl: 'http://superset.com',
      });
      compareURI(URI(url), URI('/superset/explore/'));
    });
    it('generates proper json url', () => {
      const url = getExploreUrl({
        formData,
        endpointType: 'json',
        force: false,
        curUrl: 'http://superset.com',
      });
      compareURI(URI(url), URI('/superset/explore_json/'));
    });
    it('generates proper json forced url', () => {
      const url = getExploreUrl({
        formData,
        endpointType: 'json',
        force: true,
        curUrl: 'superset.com',
      });
      compareURI(
        URI(url),
        URI('/superset/explore_json/').search({ force: 'true' }),
      );
    });
    it('generates proper csv URL', () => {
      const url = getExploreUrl({
        formData,
        endpointType: 'csv',
        force: false,
        curUrl: 'superset.com',
      });
      compareURI(
        URI(url),
        URI('/superset/explore_json/').search({ csv: 'true' }),
      );
    });
    it('generates proper standalone URL', () => {
      const url = getExploreUrl({
        formData,
        endpointType: 'standalone',
        force: false,
        curUrl: 'superset.com',
      });
      compareURI(
        URI(url),
        URI('/superset/explore/').search({ standalone: 'true' }),
      );
    });
    it('preserves main URLs params', () => {
      const url = getExploreUrl({
        formData,
        endpointType: 'json',
        force: false,
        curUrl: 'superset.com?foo=bar',
      });
      compareURI(
        URI(url),
        URI('/superset/explore_json/').search({ foo: 'bar' }),
      );
    });
    it('generate proper save slice url', () => {
      const url = getExploreUrl({
        formData,
        endpointType: 'json',
        force: false,
        curUrl: 'superset.com?foo=bar',
      });
      compareURI(
        URI(url),
        URI('/superset/explore_json/').search({ foo: 'bar' }),
      );
    });
  });

  describe('domain sharding', () => {
    let stub;
    const availableDomains = [
      'http://localhost/',
      'domain1.com',
      'domain2.com',
      'domain3.com',
    ];
    beforeEach(() => {
      stub = sinon
        .stub(hostNamesConfig, 'availableDomains')
        .value(availableDomains);
    });
    afterEach(() => {
      stub.restore();
    });

    it('generate url to different domains', () => {
      let url = getExploreUrl({
        formData,
        endpointType: 'json',
        allowDomainSharding: true,
      });
      // skip main domain for fetching chart if domain sharding is enabled
      // to leave main domain free for other calls like fav star, save change, etc.
      expect(url).toMatch(availableDomains[1]);

      url = getExploreUrl({
        formData,
        endpointType: 'json',
        allowDomainSharding: true,
      });
      expect(url).toMatch(availableDomains[2]);

      url = getExploreUrl({
        formData,
        endpointType: 'json',
        allowDomainSharding: true,
      });
      expect(url).toMatch(availableDomains[3]);

      // circle back to first available domain
      url = getExploreUrl({
        formData,
        endpointType: 'json',
        allowDomainSharding: true,
      });
      expect(url).toMatch(availableDomains[1]);
    });
    it('not generate url to different domains without flag', () => {
      let csvURL = getExploreUrl({
        formData,
        endpointType: 'csv',
      });
      expect(csvURL).toMatch(availableDomains[0]);

      csvURL = getExploreUrl({
        formData,
        endpointType: 'csv',
      });
      expect(csvURL).toMatch(availableDomains[0]);
    });
  });

  describe('getExploreLongUrl', () => {
    it('generates proper base url with form_data', () => {
      compareURI(
        URI(getExploreLongUrl(formData, 'base')),
        URI('/superset/explore/').search({ form_data: sFormData }),
      );
    });
  });

  describe('getDataTablePageSize', () => {
    it('divides samples data into pages dynamically', () => {
      let pageSize;
      pageSize = getDataTablePageSize(500);
      expect(pageSize).toEqual(20);
      pageSize = getDataTablePageSize(0);
      expect(pageSize).toEqual(50);
      pageSize = getDataTablePageSize(1);
      expect(pageSize).toEqual(10000);
      pageSize = getDataTablePageSize(1000000);
      expect(pageSize).toEqual(5);
    });
  });

  describe('buildV1ChartDataPayload', () => {
    it('generate valid request payload despite no registered buildQuery', () => {
      const v1RequestPayload = buildV1ChartDataPayload({
        formData: { ...formData, viz_type: 'my_custom_viz' },
      });
      expect(v1RequestPayload).hasOwnProperty('queries');
    });
  });

  describe('shouldUseLegacyApi', () => {
    beforeAll(() => {
      getChartMetadataRegistry()
        .registerValue('my_legacy_viz', { useLegacyApi: true })
        .registerValue('my_v1_viz', { useLegacyApi: false });
    });

    afterAll(() => {
      getChartMetadataRegistry().remove('my_legacy_viz').remove('my_v1_viz');
    });

    it('returns true for legacy viz', () => {
      const useLegacyApi = shouldUseLegacyApi({
        ...formData,
        viz_type: 'my_legacy_viz',
      });
      expect(useLegacyApi).toBe(true);
    });

    it('returns false for v1 viz', () => {
      const useLegacyApi = shouldUseLegacyApi({
        ...formData,
        viz_type: 'my_v1_viz',
      });
      expect(useLegacyApi).toBe(false);
    });

    it('returns false for formData with unregistered viz_type', () => {
      const useLegacyApi = shouldUseLegacyApi({
        ...formData,
        viz_type: 'undefined_viz',
      });
      expect(useLegacyApi).toBe(false);
    });

    it('returns false for formData without viz_type', () => {
      const useLegacyApi = shouldUseLegacyApi(formData);
      expect(useLegacyApi).toBe(false);
    });
  });

  describe('buildTimeRangeString', () => {
    it('generates proper time range string', () => {
      expect(
        buildTimeRangeString('2010-07-30T00:00:00', '2020-07-30T00:00:00'),
      ).toBe('2010-07-30T00:00:00 : 2020-07-30T00:00:00');
      expect(buildTimeRangeString('', '2020-07-30T00:00:00')).toBe(
        ' : 2020-07-30T00:00:00',
      );
      expect(buildTimeRangeString('', '')).toBe(' : ');
    });
  });

  describe('formatTimeRange', () => {
    it('generates a readable time range', () => {
      expect(formatTimeRange('Last 7 days')).toBe('Last 7 days');
      expect(formatTimeRange('No filter')).toBe('No filter');
      expect(formatTimeRange('Yesterday : Tomorrow')).toBe(
        'Yesterday < col < Tomorrow',
      );
      expect(
        formatTimeRange('2010-07-30T00:00:00 : 2020-07-30T00:00:00', [
          'inclusive',
          'exclusive',
        ]),
      ).toBe('2010-07-30 ≤ col < 2020-07-30');
      expect(
        formatTimeRange('2010-07-30T01:00:00 : ', ['exclusive', 'inclusive']),
      ).toBe('2010-07-30T01:00:00 < col ≤ ∞');
      expect(formatTimeRange(' : 2020-07-30T00:00:00')).toBe(
        '-∞ < col < 2020-07-30',
      );
    });
  });

  describe('getSimpleSQLExpression', () => {
    const subject = 'subject';
    const operator = '=';
    const comparator = 'comparator';
    it('returns empty string when subject is undefined', () => {
      expect(getSimpleSQLExpression(undefined, '=', 10)).toBe('');
      expect(getSimpleSQLExpression()).toBe('');
    });
    it('returns subject when its provided and operator is undefined', () => {
      expect(getSimpleSQLExpression(subject, undefined, 10)).toBe(subject);
      expect(getSimpleSQLExpression(subject)).toBe(subject);
    });
    it('returns subject and operator when theyre provided and comparator is undefined', () => {
      expect(getSimpleSQLExpression(subject, operator)).toBe(
        `${subject} ${operator}`,
      );
    });
    it('returns full expression when subject, operator and comparator are provided', () => {
      expect(getSimpleSQLExpression(subject, operator, comparator)).toBe(
        `${subject} ${operator} ${comparator}`,
      );
      expect(getSimpleSQLExpression(subject, operator, comparator, true)).toBe(
        `${subject} ${operator} ('${comparator}')`,
      );
    });
  });
});
