/**
 * 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,
  exploreChart,
  getExploreUrl,
  getSimpleSQLExpression,
  getQuerySettings,
} from 'src/explore/exploreUtils';
import { DashboardStandaloneMode } from 'src/dashboard/util/constants';
import * as hostNamesConfig from 'src/utils/hostNamesConfig';
import { getChartMetadataRegistry, SupersetClient } from '@superset-ui/core';

// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
describe('exploreUtils', () => {
  const { location } = window;
  const formData = {
    datasource: '1__table',
  };
  function compareURI(uri1, uri2) {
    expect(uri1.toString()).toBe(uri2.toString());
  }

  // eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
  describe('getExploreUrl', () => {
    test('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('/explore/'));
    });
    test('generates proper json url', () => {
      const url = getExploreUrl({
        formData,
        endpointType: 'json',
        force: false,
        curUrl: 'http://superset.com',
      });
      compareURI(URI(url), URI('/superset/explore_json/'));
    });
    test('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' }),
      );
    });
    test('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' }),
      );
    });
    test('generates proper standalone URL', () => {
      const url = getExploreUrl({
        formData,
        endpointType: 'standalone',
        force: false,
        curUrl: 'superset.com',
      });
      compareURI(
        URI(url),
        URI('/explore/').search({
          standalone: DashboardStandaloneMode.HideNav,
        }),
      );
    });
    test('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' }),
      );
    });
    test('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' }),
      );
    });
  });

  // eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
  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();
    });

    test('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]);
    });
    test('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]);
    });
  });

  // eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
  describe('buildV1ChartDataPayload', () => {
    test('generate valid request payload despite no registered buildQuery', async () => {
      const v1RequestPayload = await buildV1ChartDataPayload({
        formData: { ...formData, viz_type: 'my_custom_viz' },
      });
      expect(v1RequestPayload.hasOwnProperty('queries')).toBeTruthy();
    });
  });

  // eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
  describe('getQuerySettings', () => {
    beforeAll(() => {
      getChartMetadataRegistry()
        .registerValue('my_legacy_viz', { useLegacyApi: true })
        .registerValue('my_v1_viz', { useLegacyApi: false });
    });

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

    test('returns true for legacy viz', () => {
      const [useLegacyApi, parseMethod] = getQuerySettings({
        ...formData,
        viz_type: 'my_legacy_viz',
      });
      expect(useLegacyApi).toBe(true);
      expect(parseMethod).toBe('json-bigint');
    });

    test('returns false for v1 viz', () => {
      const [useLegacyApi, parseMethod] = getQuerySettings({
        ...formData,
        viz_type: 'my_v1_viz',
      });
      expect(useLegacyApi).toBe(false);
      expect(parseMethod).toBe('json-bigint');
    });

    test('returns false for formData with unregistered viz_type', () => {
      const [useLegacyApi, parseMethod] = getQuerySettings({
        ...formData,
        viz_type: 'undefined_viz',
      });
      expect(useLegacyApi).toBe(false);
      expect(parseMethod).toBe('json-bigint');
    });

    test('returns false for formData without viz_type', () => {
      const [useLegacyApi, parseMethod] = getQuerySettings(formData);
      expect(useLegacyApi).toBe(false);
      expect(parseMethod).toBe('json-bigint');
    });
  });

  // eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
  describe('getSimpleSQLExpression', () => {
    test('returns empty string when subject is undefined', () => {
      expect(getSimpleSQLExpression(undefined, '=', 10)).toBe('');
      expect(getSimpleSQLExpression()).toBe('');
    });
    test("returns subject when it's provided and operator is undefined", () => {
      expect(getSimpleSQLExpression('col', undefined, 10)).toBe('col');
      expect(getSimpleSQLExpression('col')).toBe('col');
    });
    test("returns subject and operator when they're provided and comparator is undefined", () => {
      expect(getSimpleSQLExpression('col', '=')).toBe('col =');
      expect(getSimpleSQLExpression('col', 'IN')).toBe('col IN');
      expect(getSimpleSQLExpression('col', 'IN', [])).toBe('col IN');
    });
    test('returns full expression when subject, operator and comparator are provided', () => {
      expect(getSimpleSQLExpression('col', '=', 'comp')).toBe("col = 'comp'");
      expect(getSimpleSQLExpression('col', '=', "it's an apostrophe")).toBe(
        "col = 'it''s an apostrophe'",
      );
      expect(getSimpleSQLExpression('col', '=', 0)).toBe('col = 0');
      expect(getSimpleSQLExpression('col', '=', '0')).toBe('col = 0');
      expect(getSimpleSQLExpression('col', 'IN', 'foo')).toBe("col IN ('foo')");
      expect(getSimpleSQLExpression('col', 'NOT IN', ['foo'])).toBe(
        "col NOT IN ('foo')",
      );
      expect(getSimpleSQLExpression('col', 'IN', ['foo', 'bar'])).toBe(
        "col IN ('foo', 'bar')",
      );
      expect(getSimpleSQLExpression('col', 'IN', ['0', '1', '2'])).toBe(
        'col IN (0, 1, 2)',
      );
      expect(getSimpleSQLExpression('col', 'NOT IN', [0, 1, 2])).toBe(
        'col NOT IN (0, 1, 2)',
      );
    });
  });

  // eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
  describe('.exploreChart()', () => {
    test('postForm', () => {
      const postFormSpy = jest.spyOn(SupersetClient, 'postForm');
      postFormSpy.mockImplementation(jest.fn());

      exploreChart({
        formData: { ...formData, viz_type: 'my_custom_viz' },
      });
      expect(postFormSpy).toHaveBeenCalledTimes(1);
    });
  });
});
