/**
 * 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 { ChangeEvent, EventHandler, useState, useEffect } from 'react';
import cx from 'classnames';
import {
  t,
  DatabaseConnectionExtension,
  isFeatureEnabled,
  useTheme,
  FeatureFlag,
} from '@superset-ui/core';
import {
  Input,
  Checkbox,
  Collapse,
  InfoTooltip,
  CollapseLabelInModal,
  type CheckboxChangeEvent,
} from '@superset-ui/core/components';
import { useJsonValidation } from '@superset-ui/core/components/AsyncAceEditor';
import {
  StyledInputContainer,
  StyledJsonEditor,
  StyledExpandableForm,
  no_margin_bottom,
} from './styles';
import { DatabaseObject, ExtraJson } from '../types';

const ExtraOptions = ({
  db,
  onInputChange,
  onTextChange,
  onEditorChange,
  onExtraInputChange,
  onExtraEditorChange,
  extraExtension,
}: {
  db: DatabaseObject | null;
  onInputChange: (
    e: CheckboxChangeEvent | React.ChangeEvent<HTMLInputElement>,
  ) => void;
  onTextChange: EventHandler<ChangeEvent<HTMLTextAreaElement>>;
  onEditorChange: Function;
  onExtraInputChange: (
    e: CheckboxChangeEvent | ChangeEvent<HTMLInputElement>,
  ) => void;
  onExtraEditorChange: Function;
  extraExtension: DatabaseConnectionExtension | undefined;
}) => {
  const expandableModalIsOpen = !!db?.expose_in_sqllab;
  const createAsOpen = !!(db?.allow_ctas || db?.allow_cvas);
  const isFileUploadSupportedByEngine =
    db?.engine_information?.supports_file_upload;
  const supportsDynamicCatalog =
    db?.engine_information?.supports_dynamic_catalog;

  // JSON.parse will deep parse engine_params
  // if it's an object, and we want to keep it a string
  const extraJson: ExtraJson = JSON.parse(db?.extra || '{}', (key, value) => {
    if (key === 'engine_params' && typeof value === 'object') {
      // keep this as a string
      return JSON.stringify(value);
    }
    return value;
  });

  // JSON validation hooks for the three editors
  const secureExtraAnnotations = useJsonValidation(db?.masked_encrypted_extra, {
    errorPrefix: 'Invalid secure extra JSON',
  });

  const metadataParamsValue = !Object.keys(extraJson?.metadata_params || {})
    .length
    ? ''
    : typeof extraJson?.metadata_params === 'string'
      ? extraJson?.metadata_params
      : JSON.stringify(extraJson?.metadata_params);
  const metadataParamsAnnotations = useJsonValidation(metadataParamsValue, {
    errorPrefix: 'Invalid metadata parameters JSON',
  });

  const engineParamsValue = !Object.keys(extraJson?.engine_params || {}).length
    ? ''
    : typeof extraJson?.engine_params === 'string'
      ? extraJson?.engine_params
      : JSON.stringify(extraJson?.engine_params);
  const engineParamsAnnotations = useJsonValidation(engineParamsValue, {
    errorPrefix: 'Invalid engine parameters JSON',
  });

  const theme = useTheme();
  const ExtraExtensionComponent = extraExtension?.component;
  const ExtraExtensionLogo = extraExtension?.logo;
  const ExtensionDescription = extraExtension?.description;
  const allowRunAsync = isFeatureEnabled(FeatureFlag.ForceSqlLabRunAsync)
    ? true
    : !!db?.allow_run_async;
  const isAllowRunAsyncDisabled = isFeatureEnabled(
    FeatureFlag.ForceSqlLabRunAsync,
  );
  const [activeKey, setActiveKey] = useState<string[] | undefined>();

  useEffect(() => {
    if (!expandableModalIsOpen && activeKey !== undefined) {
      setActiveKey(undefined);
    }
    // See issue #34630 for why we omit `activeKey` from the dependency array
  }, [expandableModalIsOpen]);

  return (
    <Collapse
      expandIconPosition="end"
      accordion
      modalMode
      activeKey={activeKey}
      onChange={key => setActiveKey(key)}
      items={[
        {
          key: 'sql-lab',
          label: (
            <CollapseLabelInModal
              title={t('SQL Lab')}
              subtitle={t(
                'Adjust how this database will interact with SQL Lab.',
              )}
              testId="sql-lab-label-test"
            />
          ),
          children: (
            <>
              <StyledInputContainer css={no_margin_bottom}>
                <div className="input-container">
                  <Checkbox
                    id="expose_in_sqllab"
                    name="expose_in_sqllab"
                    indeterminate={false}
                    checked={!!db?.expose_in_sqllab}
                    onChange={onInputChange}
                  >
                    {t('Expose database in SQL Lab')}
                  </Checkbox>
                  <InfoTooltip
                    tooltip={t('Allow this database to be queried in SQL Lab')}
                  />
                </div>
                <StyledExpandableForm
                  className={cx('expandable', {
                    open: expandableModalIsOpen,
                    'ctas-open': createAsOpen,
                  })}
                >
                  <StyledInputContainer css={no_margin_bottom}>
                    <div className="input-container">
                      <Checkbox
                        id="allow_ctas"
                        name="allow_ctas"
                        indeterminate={false}
                        checked={!!db?.allow_ctas}
                        onChange={onInputChange}
                      >
                        {t('Allow CREATE TABLE AS')}
                      </Checkbox>
                      <InfoTooltip
                        tooltip={t(
                          'Allow creation of new tables based on queries',
                        )}
                      />
                    </div>
                  </StyledInputContainer>
                  <StyledInputContainer css={no_margin_bottom}>
                    <div className="input-container">
                      <Checkbox
                        id="allow_cvas"
                        name="allow_cvas"
                        indeterminate={false}
                        checked={!!db?.allow_cvas}
                        onChange={onInputChange}
                      >
                        {t('Allow CREATE VIEW AS')}
                      </Checkbox>
                      <InfoTooltip
                        tooltip={t(
                          'Allow creation of new views based on queries',
                        )}
                      />
                    </div>
                    <StyledInputContainer
                      className={cx('expandable', { open: createAsOpen })}
                    >
                      <div className="control-label">
                        {t('CTAS & CVAS SCHEMA')}
                      </div>
                      <div className="input-container">
                        <Input
                          type="text"
                          name="force_ctas_schema"
                          placeholder={t('Create or select schema...')}
                          onChange={onInputChange}
                          value={db?.force_ctas_schema || ''}
                        />
                      </div>
                      <div className="helper">
                        {t(
                          'Force all tables and views to be created in this schema when clicking CTAS or CVAS in SQL Lab.',
                        )}
                      </div>
                    </StyledInputContainer>
                  </StyledInputContainer>
                  <StyledInputContainer css={no_margin_bottom}>
                    <div className="input-container">
                      <Checkbox
                        id="allow_dml"
                        name="allow_dml"
                        indeterminate={false}
                        checked={!!db?.allow_dml}
                        onChange={onInputChange}
                      >
                        {t('Allow DDL and DML')}
                      </Checkbox>
                      <InfoTooltip
                        tooltip={t(
                          'Allow the execution of DDL (Data Definition Language: CREATE, DROP, TRUNCATE, etc.) and DML (Data Modification Language: INSERT, UPDATE, DELETE, etc)',
                        )}
                      />
                    </div>
                  </StyledInputContainer>
                  <StyledInputContainer css={no_margin_bottom}>
                    <div className="input-container">
                      <Checkbox
                        id="cost_estimate_enabled"
                        name="cost_estimate_enabled"
                        indeterminate={false}
                        checked={!!extraJson?.cost_estimate_enabled}
                        onChange={onExtraInputChange}
                      >
                        {t('Enable query cost estimation')}
                      </Checkbox>
                      <InfoTooltip
                        tooltip={t(
                          'For Bigquery, Presto and Postgres, shows a button to compute cost before running a query.',
                        )}
                      />
                    </div>
                  </StyledInputContainer>
                  <StyledInputContainer css={no_margin_bottom}>
                    <div className="input-container">
                      <Checkbox
                        id="allows_virtual_table_explore"
                        name="allows_virtual_table_explore"
                        indeterminate={false}
                        // when `allows_virtual_table_explore` is not present in `extra` it defaults to true
                        checked={
                          extraJson?.allows_virtual_table_explore !== false
                        }
                        onChange={onExtraInputChange}
                      >
                        {t('Allow this database to be explored')}
                      </Checkbox>
                      <InfoTooltip
                        tooltip={t(
                          'When enabled, users are able to visualize SQL Lab results in Explore.',
                        )}
                      />
                    </div>
                  </StyledInputContainer>
                  <StyledInputContainer css={no_margin_bottom}>
                    <div className="input-container">
                      <Checkbox
                        id="disable_data_preview"
                        name="disable_data_preview"
                        indeterminate={false}
                        checked={!!extraJson?.disable_data_preview}
                        onChange={onExtraInputChange}
                      >
                        {t('Disable SQL Lab data preview queries')}
                      </Checkbox>
                      <InfoTooltip
                        tooltip={t(
                          'Disable data preview when fetching table metadata in SQL Lab. ' +
                            ' Useful to avoid browser performance issues when using ' +
                            ' databases with very wide tables.',
                        )}
                      />
                    </div>
                  </StyledInputContainer>
                  <StyledInputContainer>
                    <div className="input-container">
                      <Checkbox
                        id="expand_rows"
                        name="expand_rows"
                        indeterminate={false}
                        checked={!!extraJson?.schema_options?.expand_rows}
                        onChange={onExtraInputChange}
                      >
                        {t('Enable row expansion in schemas')}
                      </Checkbox>
                      <InfoTooltip
                        tooltip={t(
                          'For Trino, describe full schemas of nested ROW types, expanding them with dotted paths',
                        )}
                      />
                    </div>
                  </StyledInputContainer>
                </StyledExpandableForm>
              </StyledInputContainer>
            </>
          ),
        },
        {
          key: 'performance',
          label: (
            <CollapseLabelInModal
              title={t('Performance')}
              subtitle={t('Adjust performance settings of this database.')}
              testId="performance-label-test"
            />
          ),
          children: (
            <>
              <StyledInputContainer className="mb-8">
                <div className="control-label">{t('Chart cache timeout')}</div>
                <div className="input-container">
                  <Input
                    type="number"
                    name="cache_timeout"
                    value={db?.cache_timeout || ''}
                    placeholder={t('Enter duration in seconds')}
                    onChange={onInputChange}
                    data-test="cache-timeout-test"
                  />
                </div>
                <div className="helper">
                  {t(
                    'Duration (in seconds) of the caching timeout for charts of this database.' +
                      ' A timeout of 0 indicates that the cache never expires, and -1 bypasses the cache.' +
                      ' Note this defaults to the global timeout if undefined.',
                  )}
                </div>
              </StyledInputContainer>
              <StyledInputContainer>
                <div className="control-label">{t('Schema cache timeout')}</div>
                <div className="input-container">
                  <Input
                    type="number"
                    name="schema_cache_timeout"
                    value={
                      extraJson?.metadata_cache_timeout?.schema_cache_timeout ||
                      ''
                    }
                    placeholder={t('Enter duration in seconds')}
                    onChange={onExtraInputChange}
                    data-test="schema-cache-timeout-test"
                  />
                </div>
                <div className="helper">
                  {t(
                    'Duration (in seconds) of the metadata caching timeout for schemas of ' +
                      'this database. If left unset, the cache never expires.',
                  )}
                </div>
              </StyledInputContainer>
              <StyledInputContainer>
                <div className="control-label">{t('Table cache timeout')}</div>
                <div className="input-container">
                  <Input
                    type="number"
                    name="table_cache_timeout"
                    value={
                      extraJson?.metadata_cache_timeout?.table_cache_timeout ||
                      ''
                    }
                    placeholder={t('Enter duration in seconds')}
                    onChange={onExtraInputChange}
                    data-test="table-cache-timeout-test"
                  />
                </div>
                <div className="helper">
                  {t(
                    'Duration (in seconds) of the metadata caching timeout for tables of ' +
                      'this database. If left unset, the cache never expires. ',
                  )}
                </div>
              </StyledInputContainer>
              <StyledInputContainer css={{ no_margin_bottom }}>
                <div className="input-container">
                  <Checkbox
                    id="allow_run_async"
                    name="allow_run_async"
                    indeterminate={false}
                    checked={allowRunAsync}
                    onChange={onInputChange}
                  >
                    {t('Asynchronous query execution')}
                  </Checkbox>
                  <InfoTooltip
                    tooltip={t(
                      'Operate the database in asynchronous mode, meaning that the queries ' +
                        'are executed on remote workers as opposed to on the web server itself. ' +
                        'This assumes that you have a Celery worker setup as well as a results ' +
                        'backend. Refer to the installation docs for more information.',
                    )}
                  />
                  {isAllowRunAsyncDisabled && (
                    <InfoTooltip
                      iconStyle={{ color: theme.colorError }}
                      tooltip={t(
                        'This option has been disabled by the administrator.',
                      )}
                    />
                  )}
                </div>
              </StyledInputContainer>
              <StyledInputContainer css={{ no_margin_bottom }}>
                <div className="input-container">
                  <Checkbox
                    id="cancel_query_on_windows_unload"
                    name="cancel_query_on_windows_unload"
                    indeterminate={false}
                    checked={!!extraJson?.cancel_query_on_windows_unload}
                    onChange={onExtraInputChange}
                  >
                    {t('Cancel query on window unload event')}
                  </Checkbox>
                  <InfoTooltip
                    tooltip={t(
                      'Terminate running queries when browser window closed or navigated ' +
                        'to another page. Available for Presto, Hive, MySQL, Postgres and ' +
                        'Snowflake databases.',
                    )}
                  />
                </div>
              </StyledInputContainer>
            </>
          ),
        },
        {
          key: 'security',
          label: (
            <CollapseLabelInModal
              title={t('Security')}
              testId="security-label-test"
              subtitle={t('Add extra connection information.')}
            />
          ),
          children: (
            <>
              <StyledInputContainer
                css={!isFileUploadSupportedByEngine ? no_margin_bottom : {}}
              >
                <div className="input-container">
                  <Checkbox
                    id="per_user_caching"
                    name="per_user_caching"
                    indeterminate={false}
                    checked={!!extraJson?.per_user_caching}
                    onChange={onExtraInputChange}
                  >
                    {t('Per user caching')}
                  </Checkbox>
                  <InfoTooltip
                    tooltip={t(
                      'Cache data separately for each user based on their data access roles and permissions. ' +
                        'When disabled, a single cache will be used for all users.',
                    )}
                  />
                </div>
              </StyledInputContainer>
              <StyledInputContainer
                css={!isFileUploadSupportedByEngine ? no_margin_bottom : {}}
              >
                <div className="input-container">
                  <Checkbox
                    id="impersonate_user"
                    name="impersonate_user"
                    indeterminate={false}
                    checked={!!db?.impersonate_user}
                    onChange={onInputChange}
                  >
                    {t(
                      'Impersonate logged in user (Presto, Trino, Drill, Hive, and Google Sheets)',
                    )}
                  </Checkbox>
                  <InfoTooltip
                    tooltip={t(
                      'If Presto or Trino, all the queries in SQL Lab are going to be executed as the ' +
                        'currently logged on user who must have permission to run them. If Hive ' +
                        'and hive.server2.enable.doAs is enabled, will run the queries as ' +
                        'service account, but impersonate the currently logged on user via ' +
                        'hive.server2.proxy.user property.',
                    )}
                  />
                </div>
              </StyledInputContainer>
              {isFileUploadSupportedByEngine && (
                <StyledInputContainer
                  css={!db?.allow_file_upload ? no_margin_bottom : {}}
                >
                  <div className="input-container">
                    <Checkbox
                      id="allow_file_upload"
                      name="allow_file_upload"
                      indeterminate={false}
                      checked={!!db?.allow_file_upload}
                      onChange={onInputChange}
                    >
                      {t('Allow file uploads to database')}
                    </Checkbox>
                  </div>
                </StyledInputContainer>
              )}
              {isFileUploadSupportedByEngine && !!db?.allow_file_upload && (
                <StyledInputContainer css={no_margin_bottom}>
                  <div className="control-label">
                    {t('Schemas allowed for File upload')}
                  </div>
                  <div className="input-container">
                    <Input
                      type="text"
                      name="schemas_allowed_for_file_upload"
                      value={(
                        extraJson?.schemas_allowed_for_file_upload || []
                      ).join(',')}
                      placeholder="schema1,schema2"
                      onChange={onExtraInputChange}
                    />
                  </div>
                  <div className="helper">
                    {t(
                      'A comma-separated list of schemas that files are allowed to upload to.',
                    )}
                  </div>
                </StyledInputContainer>
              )}
              <StyledInputContainer>
                <div className="control-label">{t('Secure extra')}</div>
                <div className="input-container">
                  <StyledJsonEditor
                    name="masked_encrypted_extra"
                    value={db?.masked_encrypted_extra || ''}
                    placeholder={t('Secure extra')}
                    onChange={(json: string) =>
                      onEditorChange({ json, name: 'masked_encrypted_extra' })
                    }
                    width="100%"
                    height="160px"
                    annotations={secureExtraAnnotations}
                  />
                </div>
                <div className="helper">
                  <div>
                    {t(
                      'JSON string containing additional connection configuration. ' +
                        'This is used to provide connection information for systems ' +
                        'like Hive, Presto and BigQuery which do not conform to the ' +
                        'username:password syntax normally used by SQLAlchemy.',
                    )}
                  </div>
                </div>
              </StyledInputContainer>
              <StyledInputContainer>
                <div className="control-label">{t('Root certificate')}</div>
                <div className="input-container">
                  <Input.TextArea
                    name="server_cert"
                    value={db?.server_cert || ''}
                    placeholder={t('Enter CA_BUNDLE')}
                    onChange={onTextChange}
                  />
                </div>
                <div className="helper">
                  {t(
                    'Optional CA_BUNDLE contents to validate HTTPS requests. Only ' +
                      'available on certain database engines.',
                  )}
                </div>
              </StyledInputContainer>
            </>
          ),
        },
        ...(extraExtension && ExtraExtensionComponent && ExtensionDescription
          ? [
              {
                key: extraExtension?.title,
                collapsible: extraExtension.enabled?.()
                  ? ('icon' as const)
                  : ('disabled' as const),
                label: (
                  <CollapseLabelInModal
                    key={extraExtension?.title}
                    title={
                      <>
                        {ExtraExtensionLogo && <ExtraExtensionLogo />}
                        {extraExtension?.title}
                      </>
                    }
                    subtitle={<ExtensionDescription />}
                  />
                ),
                children: (
                  <StyledInputContainer css={no_margin_bottom}>
                    <ExtraExtensionComponent
                      db={db}
                      onEdit={extraExtension.onEdit}
                    />
                  </StyledInputContainer>
                ),
              },
            ]
          : []),
        {
          key: 'other',
          label: (
            <CollapseLabelInModal
              title={t('Other')}
              subtitle={t('Additional settings.')}
              testId="other-label-test"
            />
          ),
          children: (
            <>
              <StyledInputContainer>
                <div className="control-label">{t('Metadata Parameters')}</div>
                <div className="input-container">
                  <StyledJsonEditor
                    name="metadata_params"
                    placeholder={t('Metadata Parameters')}
                    onChange={(json: string) =>
                      onExtraEditorChange({ json, name: 'metadata_params' })
                    }
                    width="100%"
                    height="160px"
                    value={
                      !Object.keys(extraJson?.metadata_params || {}).length
                        ? ''
                        : typeof extraJson?.metadata_params === 'string'
                          ? extraJson?.metadata_params
                          : JSON.stringify(extraJson?.metadata_params)
                    }
                    annotations={metadataParamsAnnotations}
                  />
                </div>
                <div className="helper">
                  <div>
                    {t(
                      'The metadata_params object gets unpacked into the sqlalchemy.MetaData call.',
                    )}
                  </div>
                </div>
              </StyledInputContainer>
              <StyledInputContainer>
                <div className="control-label">{t('Engine Parameters')}</div>
                <div className="input-container">
                  <StyledJsonEditor
                    name="engine_params"
                    placeholder={t('Engine Parameters')}
                    onChange={(json: string) =>
                      onExtraEditorChange({ json, name: 'engine_params' })
                    }
                    width="100%"
                    height="160px"
                    value={
                      !Object.keys(extraJson?.engine_params || {}).length
                        ? ''
                        : extraJson?.engine_params
                    }
                    annotations={engineParamsAnnotations}
                  />
                </div>
                <div className="helper">
                  <div>
                    {t(
                      'The engine_params object gets unpacked into the sqlalchemy.create_engine call.',
                    )}
                  </div>
                </div>
              </StyledInputContainer>
              <StyledInputContainer>
                <div className="control-label" data-test="version-label-test">
                  {t('Version')}
                </div>
                <div
                  className="input-container"
                  data-test="version-spinbutton-test"
                >
                  <Input
                    type="text"
                    name="version"
                    placeholder={t('Version number')}
                    onChange={onExtraInputChange}
                    value={extraJson?.version || ''}
                  />
                </div>
                <div className="helper">
                  {t(
                    'Specify the database version. This is used with Presto for query cost ' +
                      'estimation, and Dremio for syntax changes, among others.',
                  )}
                </div>
              </StyledInputContainer>
              <StyledInputContainer css={no_margin_bottom}>
                <div className="input-container">
                  <Checkbox
                    id="disable_drill_to_detail"
                    name="disable_drill_to_detail"
                    indeterminate={false}
                    checked={!!extraJson?.disable_drill_to_detail}
                    onChange={onExtraInputChange}
                  >
                    {t('Disable drill to detail')}
                  </Checkbox>
                  <InfoTooltip
                    tooltip={t(
                      'Disables the drill to detail feature for this database.',
                    )}
                  />
                </div>
              </StyledInputContainer>
              {supportsDynamicCatalog && (
                <StyledInputContainer css={no_margin_bottom}>
                  <div className="input-container">
                    <Checkbox
                      id="allow_multi_catalog"
                      name="allow_multi_catalog"
                      indeterminate={false}
                      checked={!!extraJson?.allow_multi_catalog}
                      onChange={onExtraInputChange}
                    >
                      {t('Allow changing catalogs')}
                    </Checkbox>
                    <InfoTooltip
                      tooltip={t(
                        'Give access to multiple catalogs in a single database connection.',
                      )}
                    />
                  </div>
                </StyledInputContainer>
              )}
            </>
          ),
        },
      ]}
    />
  );
};

export default ExtraOptions;
