/* eslint-disable camelcase */
/**
 * 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 { PureComponent } from 'react';
import PropTypes from 'prop-types';
import {
  css,
  DatasourceType,
  SupersetClient,
  styled,
  t,
  withTheme,
} from '@superset-ui/core';
import { getTemporalColumns } from '@superset-ui/chart-controls';
import { getUrlParam } from 'src/utils/urlUtils';
import {
  Dropdown,
  Tooltip,
  Button,
  ModalTrigger,
} from '@superset-ui/core/components';
import {
  ChangeDatasourceModal,
  DatasourceModal,
  ErrorAlert,
} from 'src/components';
import { Menu } from '@superset-ui/core/components/Menu';
import { Icons } from '@superset-ui/core/components/Icons';
import WarningIconWithTooltip from '@superset-ui/core/components/WarningIconWithTooltip';
import { URL_PARAMS } from 'src/constants';
import { getDatasourceAsSaveableDataset } from 'src/utils/datasourceUtils';
import {
  userHasPermission,
  isUserAdmin,
} from 'src/dashboard/util/permissionUtils';
import { ErrorMessageWithStackTrace } from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
import ViewQueryModalFooter from 'src/explore/components/controls/ViewQueryModalFooter';
import ViewQuery from 'src/explore/components/controls/ViewQuery';
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
import { safeStringify } from 'src/utils/safeStringify';
import { Link } from 'react-router-dom';

const propTypes = {
  actions: PropTypes.object.isRequired,
  onChange: PropTypes.func,
  value: PropTypes.string,
  datasource: PropTypes.object.isRequired,
  form_data: PropTypes.object.isRequired,
  isEditable: PropTypes.bool,
  onDatasourceSave: PropTypes.func,
};

const defaultProps = {
  onChange: () => {},
  onDatasourceSave: null,
  value: null,
  isEditable: true,
};

const getDatasetType = datasource => {
  if (datasource.type === 'query') {
    return 'query';
  }
  if (datasource.type === 'table' && datasource.sql) {
    return 'virtual_dataset';
  }
  return 'physical_dataset';
};

const Styles = styled.div`
  .data-container {
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-bottom: 1px solid ${({ theme }) => theme.colorSplit};
    padding: ${({ theme }) => 4 * theme.sizeUnit}px;
    padding-right: ${({ theme }) => 2 * theme.sizeUnit}px;
  }
  .error-alert {
    margin: ${({ theme }) => 2 * theme.sizeUnit}px;
    min-height: 150px;
  }
  .ant-dropdown-trigger {
    margin-left: ${({ theme }) => 2 * theme.sizeUnit}px;
  }
  .btn-group .open .dropdown-toggle {
    box-shadow: none;
    &.button-default {
      background: none;
    }
  }
  i.angle {
    color: ${({ theme }) => theme.colorPrimary};
  }
  svg.datasource-modal-trigger {
    color: ${({ theme }) => theme.colorPrimary};
    cursor: pointer;
  }
  .title-select {
    flex: 1 1 100%;
    display: inline-block;
    padding: ${({ theme }) => theme.sizeUnit * 2}px 0px;
    border-radius: ${({ theme }) => theme.borderRadius}px;
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
  }
  .datasource-svg {
    margin-right: ${({ theme }) => 2 * theme.sizeUnit}px;
    flex: none;
  }
  span[aria-label='dataset-physical'] {
    color: ${({ theme }) => theme.colorIcon};
  }
  span[aria-label='more'] {
    color: ${({ theme }) => theme.colorPrimary};
  }
`;

const CHANGE_DATASET = 'change_dataset';
const VIEW_IN_SQL_LAB = 'view_in_sql_lab';
const EDIT_DATASET = 'edit_dataset';
const QUERY_PREVIEW = 'query_preview';
const SAVE_AS_DATASET = 'save_as_dataset';

// If the string is longer than this value's number characters we add
// a tooltip for user can see the full name by hovering over the visually truncated string in UI
const VISIBLE_TITLE_LENGTH = 25;

// Assign icon for each DatasourceType.  If no icon assignment is found in the lookup, no icon will render
export const datasourceIconLookup = {
  query: <Icons.ConsoleSqlOutlined className="datasource-svg" />,
  physical_dataset: <Icons.TableOutlined className="datasource-svg" />,
  virtual_dataset: <Icons.ConsoleSqlOutlined className="datasource-svg" />,
};

// Render title for datasource with tooltip only if text is longer than VISIBLE_TITLE_LENGTH
export const renderDatasourceTitle = (displayString, tooltip) =>
  displayString?.length > VISIBLE_TITLE_LENGTH ? (
    // Add a tooltip only for long names that will be visually truncated
    <Tooltip title={tooltip}>
      <span className="title-select">{displayString}</span>
    </Tooltip>
  ) : (
    <span title={tooltip} className="title-select">
      {displayString}
    </span>
  );

// Different data source types use different attributes for the display title
export const getDatasourceTitle = datasource => {
  if (datasource?.type === 'query') return datasource?.sql;
  return datasource?.name || '';
};

const preventRouterLinkWhileMetaClicked = evt => {
  if (evt.metaKey) {
    evt.preventDefault();
  } else {
    evt.stopPropagation();
  }
};

class DatasourceControl extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      showEditDatasourceModal: false,
      showChangeDatasourceModal: false,
      showSaveDatasetModal: false,
    };
  }

  onDatasourceSave = datasource => {
    this.props.actions.changeDatasource(datasource);
    const { temporalColumns, defaultTemporalColumn } =
      getTemporalColumns(datasource);
    const { columns } = datasource;
    // the current granularity_sqla might not be a temporal column anymore
    const timeCol = this.props.form_data?.granularity_sqla;
    const isGranularitySqlaTemporal = columns.find(
      ({ column_name }) => column_name === timeCol,
    )?.is_dttm;
    // the current main_dttm_col might not be a temporal column anymore
    const isDefaultTemporal = columns.find(
      ({ column_name }) => column_name === defaultTemporalColumn,
    )?.is_dttm;

    // if the current granularity_sqla is empty or it is not a temporal column anymore
    // let's update the control value
    if (datasource.type === 'table' && !isGranularitySqlaTemporal) {
      const temporalColumn = isDefaultTemporal
        ? defaultTemporalColumn
        : temporalColumns?.[0];
      this.props.actions.setControlValue(
        'granularity_sqla',
        temporalColumn || null,
      );
    }

    if (this.props.onDatasourceSave) {
      this.props.onDatasourceSave(datasource);
    }
  };

  toggleShowDatasource = () => {
    this.setState(({ showDatasource }) => ({
      showDatasource: !showDatasource,
    }));
  };

  toggleChangeDatasourceModal = () => {
    this.setState(({ showChangeDatasourceModal }) => ({
      showChangeDatasourceModal: !showChangeDatasourceModal,
    }));
  };

  toggleEditDatasourceModal = () => {
    this.setState(({ showEditDatasourceModal }) => ({
      showEditDatasourceModal: !showEditDatasourceModal,
    }));
  };

  toggleSaveDatasetModal = () => {
    this.setState(({ showSaveDatasetModal }) => ({
      showSaveDatasetModal: !showSaveDatasetModal,
    }));
  };

  handleMenuItemClick = ({ key }) => {
    switch (key) {
      case CHANGE_DATASET:
        this.toggleChangeDatasourceModal();
        break;

      case EDIT_DATASET:
        this.toggleEditDatasourceModal();
        break;

      case VIEW_IN_SQL_LAB:
        {
          const { datasource } = this.props;
          const payload = {
            datasourceKey: `${datasource.id}__${datasource.type}`,
            sql: datasource.sql,
          };
          SupersetClient.postForm('/sqllab/', {
            form_data: safeStringify(payload),
          });
        }
        break;

      case SAVE_AS_DATASET:
        this.toggleSaveDatasetModal();
        break;

      default:
        break;
    }
  };

  render() {
    const {
      showChangeDatasourceModal,
      showEditDatasourceModal,
      showSaveDatasetModal,
    } = this.state;
    const { datasource, onChange, theme } = this.props;
    let extra;
    if (datasource?.extra) {
      if (typeof datasource.extra === 'string') {
        try {
          extra = JSON.parse(datasource.extra);
        } catch {} // eslint-disable-line no-empty
      } else {
        extra = datasource.extra; // eslint-disable-line prefer-destructuring
      }
    }
    const isMissingDatasource = !datasource?.id || Boolean(extra?.error);
    let isMissingParams = false;
    if (isMissingDatasource) {
      const datasourceId = getUrlParam(URL_PARAMS.datasourceId);
      const sliceId = getUrlParam(URL_PARAMS.sliceId);

      if (!datasourceId && !sliceId) {
        isMissingParams = true;
      }
    }

    const { user } = this.props;
    const allowEdit =
      datasource.owners?.map(o => o.id || o.value).includes(user.userId) ||
      isUserAdmin(user);

    const canAccessSqlLab = userHasPermission(user, 'SQL Lab', 'menu_access');

    const editText = t('Edit dataset');
    const requestedQuery = {
      datasourceKey: `${datasource.id}__${datasource.type}`,
      sql: datasource.sql,
    };

    const defaultDatasourceMenuItems = [];
    if (this.props.isEditable && !isMissingDatasource) {
      defaultDatasourceMenuItems.push({
        key: EDIT_DATASET,
        label: !allowEdit ? (
          <Tooltip
            title={t(
              'You must be a dataset owner in order to edit. Please reach out to a dataset owner to request modifications or edit access.',
            )}
          >
            {editText}
          </Tooltip>
        ) : (
          editText
        ),
        disabled: !allowEdit,
        ...{ 'data-test': 'edit-dataset' },
      });
    }

    defaultDatasourceMenuItems.push({
      key: CHANGE_DATASET,
      label: t('Swap dataset'),
    });

    if (!isMissingDatasource && canAccessSqlLab) {
      defaultDatasourceMenuItems.push({
        key: VIEW_IN_SQL_LAB,
        label: (
          <Link
            to={{
              pathname: '/sqllab',
              state: { requestedQuery },
            }}
            onClick={preventRouterLinkWhileMetaClicked}
          >
            {t('View in SQL Lab')}
          </Link>
        ),
      });
    }

    const defaultDatasourceMenu = (
      <Menu
        onClick={this.handleMenuItemClick}
        items={defaultDatasourceMenuItems}
      />
    );

    const queryDatasourceMenuItems = [
      {
        key: QUERY_PREVIEW,
        label: (
          <ModalTrigger
            triggerNode={
              <div data-test="view-query-menu-item">{t('Query preview')}</div>
            }
            modalTitle={t('Query preview')}
            modalBody={
              <ViewQuery
                sql={datasource?.sql || datasource?.select_star || ''}
              />
            }
            modalFooter={
              <ViewQueryModalFooter
                changeDatasource={this.toggleSaveDatasetModal}
                datasource={datasource}
              />
            }
            draggable={false}
            resizable={false}
            responsive
          />
        ),
      },
    ];

    if (canAccessSqlLab) {
      queryDatasourceMenuItems.push({
        key: VIEW_IN_SQL_LAB,
        label: (
          <Link
            to={{
              pathname: '/sqllab',
              state: { requestedQuery },
            }}
            onClick={preventRouterLinkWhileMetaClicked}
          >
            {t('View in SQL Lab')}
          </Link>
        ),
      });
    }

    queryDatasourceMenuItems.push({
      key: SAVE_AS_DATASET,
      label: t('Save as dataset'),
    });

    const queryDatasourceMenu = (
      <Menu
        onClick={this.handleMenuItemClick}
        items={queryDatasourceMenuItems}
      />
    );

    const { health_check_message: healthCheckMessage } = datasource;

    const titleText =
      isMissingDatasource && !datasource.name
        ? t('Missing dataset')
        : getDatasourceTitle(datasource);

    const tooltip = titleText;

    return (
      <Styles data-test="datasource-control" className="DatasourceControl">
        <div className="data-container">
          {datasourceIconLookup[getDatasetType(datasource)]}
          {renderDatasourceTitle(titleText, tooltip)}
          {healthCheckMessage && (
            <Tooltip title={healthCheckMessage}>
              <Icons.WarningOutlined
                css={css`
                  margin-left: ${theme.sizeUnit * 2}px;
                `}
                iconColor={theme.colorWarning}
              />
            </Tooltip>
          )}
          {extra?.warning_markdown && (
            <WarningIconWithTooltip warningMarkdown={extra.warning_markdown} />
          )}
          <Dropdown
            popupRender={() =>
              datasource.type === DatasourceType.Query
                ? queryDatasourceMenu
                : defaultDatasourceMenu
            }
            trigger={['click']}
            data-test="datasource-menu"
          >
            <Icons.MoreOutlined
              iconSize="xl"
              iconColor={theme.colorPrimary}
              className="datasource-modal-trigger"
              data-test="datasource-menu-trigger"
            />
          </Dropdown>
        </div>
        {/* missing dataset */}
        {isMissingDatasource && isMissingParams && (
          <div className="error-alert">
            <ErrorAlert
              level="warning"
              errorType={t('Missing URL parameters')}
              description={t(
                'The URL is missing the dataset_id or slice_id parameters.',
              )}
            />
          </div>
        )}
        {isMissingDatasource && !isMissingParams && (
          <div className="error-alert">
            {extra?.error ? (
              <ErrorMessageWithStackTrace
                title={extra.error.statusText || extra.error.message}
                subtitle={
                  extra.error.statusText ? extra.error.message : undefined
                }
                error={extra.error}
                source="explore"
              />
            ) : (
              <ErrorAlert
                type="warning"
                errorType={t('Missing dataset')}
                descriptionPre={false}
                descriptionDetailsCollapsed={false}
                descriptionDetails={
                  <>
                    <p>
                      {t(
                        'The dataset linked to this chart may have been deleted.',
                      )}
                    </p>
                    <p>
                      <Button
                        buttonStyle="warning"
                        onClick={() =>
                          this.handleMenuItemClick({ key: CHANGE_DATASET })
                        }
                      >
                        {t('Swap dataset')}
                      </Button>
                    </p>
                  </>
                }
              />
            )}
          </div>
        )}
        {showEditDatasourceModal && (
          <DatasourceModal
            datasource={datasource}
            show={showEditDatasourceModal}
            onDatasourceSave={this.onDatasourceSave}
            onHide={this.toggleEditDatasourceModal}
          />
        )}
        {showChangeDatasourceModal && (
          <ChangeDatasourceModal
            onDatasourceSave={this.onDatasourceSave}
            onHide={this.toggleChangeDatasourceModal}
            show={showChangeDatasourceModal}
            onChange={onChange}
          />
        )}
        {showSaveDatasetModal && (
          <SaveDatasetModal
            visible={showSaveDatasetModal}
            onHide={this.toggleSaveDatasetModal}
            buttonTextOnSave={t('Save')}
            buttonTextOnOverwrite={t('Overwrite')}
            modalDescription={t(
              'Save this query as a virtual dataset to continue exploring',
            )}
            datasource={getDatasourceAsSaveableDataset(datasource)}
            openWindow={false}
            formData={this.props.form_data}
          />
        )}
      </Styles>
    );
  }
}

DatasourceControl.propTypes = propTypes;
DatasourceControl.defaultProps = defaultProps;

export default withTheme(DatasourceControl);
