/* 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 React, { useState, useCallback } from 'react';
import { DatasourceType, SupersetClient, Datasource } from '@superset-ui/core';
import { t } from '@apache-superset/core/translation';
import { css, styled, useTheme } from '@apache-superset/core/theme';
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';

// Extended Datasource interface with all properties used in this component
interface ExtendedDatasource extends Datasource {
  sql?: string;
  select_star?: string;
  owners?: Array<{
    id: number;
    first_name: string;
    last_name: string;
    value?: number;
  }>;
  extra?: string;
  health_check_message?: string;
  database?: {
    id: number;
    database_name: string;
    backend?: string;
  };
}

interface User {
  userId?: number;
  username?: string;
  roles?: Record<string, unknown[]>;
}

interface DatasourceControlActions {
  changeDatasource: (datasource: ExtendedDatasource) => void;
  setControlValue: (name: string, value: unknown) => void;
}

interface FormData {
  granularity_sqla?: string;
  [key: string]: unknown;
}

interface DatasourceControlProps {
  actions: DatasourceControlActions;
  onChange?: () => void;
  value?: string | null;
  datasource: ExtendedDatasource;
  form_data?: FormData;
  isEditable?: boolean;
  onDatasourceSave?: ((datasource: ExtendedDatasource) => void) | null;
  user: User;
  // ControlHeader-related props
  hovered?: boolean;
  type?: string;
  label?: string;
  default?: unknown;
  description?: string | null;
  validationErrors?: string[];
  name?: string;
}

const getDatasetType = (datasource: ExtendedDatasource): string => {
  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: Record<string, React.ReactNode> = {
  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: string | undefined,
  tooltip: string,
) =>
  displayString?.length && 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: ExtendedDatasource | null | undefined,
): string => {
  if (datasource?.type === 'query') return datasource?.sql || '';
  return datasource?.name || '';
};

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

export default function DatasourceControl({
  actions,
  onChange = () => {},
  value = null,
  datasource,
  form_data,
  isEditable = true,
  onDatasourceSave = null,
  user,
}: DatasourceControlProps) {
  const theme = useTheme();

  const [showEditDatasourceModal, setShowEditDatasourceModal] = useState(false);
  const [showChangeDatasourceModal, setShowChangeDatasourceModal] =
    useState(false);
  const [showSaveDatasetModal, setShowSaveDatasetModal] = useState(false);

  const handleDatasourceSave = useCallback(
    (savedDatasource: Datasource) => {
      // Cast to ExtendedDatasource for the component's internal use
      actions.changeDatasource(savedDatasource as ExtendedDatasource);
      // Cast datasource for getTemporalColumns which expects Dataset | QueryResponse
      const { temporalColumns, defaultTemporalColumn } = getTemporalColumns(
        savedDatasource as Parameters<typeof getTemporalColumns>[0],
      );
      const { columns } = savedDatasource;
      // the granularity_sqla might not be a temporal column anymore
      const timeCol = form_data?.granularity_sqla;
      const isGranularitySqlaTemporal = columns.find(
        ({ column_name }) => column_name === timeCol,
      )?.is_dttm;
      // the main_dttm_col might not be a temporal column anymore
      const isDefaultTemporal = columns.find(
        ({ column_name }) => column_name === defaultTemporalColumn,
      )?.is_dttm;

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

      if (onDatasourceSave) {
        onDatasourceSave(savedDatasource);
      }
    },
    [actions, form_data?.granularity_sqla, onDatasourceSave],
  );

  const toggleChangeDatasourceModal = useCallback(() => {
    setShowChangeDatasourceModal(prev => !prev);
  }, []);

  const toggleEditDatasourceModal = useCallback(() => {
    setShowEditDatasourceModal(prev => !prev);
  }, []);

  const toggleSaveDatasetModal = useCallback(() => {
    setShowSaveDatasetModal(prev => !prev);
  }, []);

  const handleMenuItemClick = useCallback(
    ({ key }: { key: string }) => {
      switch (key) {
        case CHANGE_DATASET:
          toggleChangeDatasourceModal();
          break;

        case EDIT_DATASET:
          toggleEditDatasourceModal();
          break;

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

        case SAVE_AS_DATASET:
          toggleSaveDatasetModal();
          break;

        default:
          break;
      }
    },
    [
      datasource,
      toggleChangeDatasourceModal,
      toggleEditDatasourceModal,
      toggleSaveDatasetModal,
    ],
  );

  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 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 (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={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 || ''}
              datasource={`${datasource.id}__${datasource.type}`}
            />
          }
          modalFooter={
            <ViewQueryModalFooter
              changeDatasource={toggleSaveDatasetModal}
              datasource={{
                id: String(datasource.id),
                sql: datasource.sql || '',
                type: datasource.type,
              }}
            />
          }
          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: <span>{t('Save as dataset')}</span>,
  });

  const queryDatasourceMenu = (
    <Menu onClick={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
            type="warning"
            message={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"
              message={t('Missing dataset')}
              descriptionPre={false}
              descriptionDetailsCollapsed={false}
              descriptionDetails={
                <>
                  <p>
                    {t(
                      'The dataset linked to this chart may have been deleted.',
                    )}
                  </p>
                  <p>
                    <Button
                      buttonStyle="primary"
                      onClick={() =>
                        handleMenuItemClick({ key: CHANGE_DATASET })
                      }
                    >
                      {t('Swap dataset')}
                    </Button>
                  </p>
                </>
              }
            />
          )}
        </div>
      )}
      {showEditDatasourceModal && (
        <DatasourceModal
          datasource={datasource}
          show={showEditDatasourceModal}
          onDatasourceSave={handleDatasourceSave}
          onHide={toggleEditDatasourceModal}
        />
      )}
      {showChangeDatasourceModal && (
        <ChangeDatasourceModal
          onDatasourceSave={handleDatasourceSave}
          onHide={toggleChangeDatasourceModal}
          show={showChangeDatasourceModal}
          onChange={onChange}
        />
      )}
      {showSaveDatasetModal && (
        <SaveDatasetModal
          visible={showSaveDatasetModal}
          onHide={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={form_data}
        />
      )}
    </Styles>
  );
}
