/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * The OpenSearch Contributors require contributions made to
 * this file be licensed under the Apache-2.0 license or a
 * compatible open source license.
 *
 * Modifications Copyright OpenSearch Contributors. See
 * GitHub history for details.
 */

import React, { Fragment, useEffect, useState } from 'react';
import { RouteComponentProps } from 'react-router';
import { useDispatch } from 'react-redux';
import { Dispatch } from 'redux';
import { FormikProps, Formik } from 'formik';
import { get, isEmpty } from 'lodash';
import {
  EuiSpacer,
  EuiFlexGroup,
  EuiFlexItem,
  EuiButton,
  EuiButtonEmpty,
  EuiPage,
  EuiPageBody,
  EuiPageHeader,
  EuiPageHeaderSection,
  EuiTitle,
} from '@elastic/eui';
import { updateDetector, matchDetector } from '../../../redux/reducers/ad';
import { useHideSideNavBar } from '../../main/hooks/useHideSideNavBar';
import { useFetchDetectorInfo } from '../../CreateDetectorSteps/hooks/useFetchDetectorInfo';
import { CoreStart } from '../../../../../../src/core/public';
import { APIAction } from '../../../redux/middleware/types';
import { CoreServicesContext } from '../../../components/CoreServices/CoreServices';
import { BREADCRUMBS } from '../../../utils/constants';
import { getErrorMessage, validateDetectorName } from '../../../utils/utils';
import { NameAndDescription } from '../components/NameAndDescription';
import { DataSource } from '../components/Datasource/DataSource';
import { CustomResultIndex } from '../components/CustomResultIndex';
import { Timestamp } from '../components/Timestamp';
import { Settings } from '../components/Settings';
import {
  detectorDefinitionToFormik,
  formikToDetectorDefinition,
  clearModelConfiguration,
} from '../utils/helpers';
import { DetectorDefinitionFormikValues } from '../models/interfaces';
import { Detector, FILTER_TYPES } from '../../../models/interfaces';
import { prettifyErrorMessage } from '../../../../server/utils/helpers';
import { DETECTOR_STATE } from '../../../../server/utils/constants';
import { ModelConfigurationFormikValues } from 'public/pages/ConfigureModel/models/interfaces';

interface DefineDetectorRouterProps {
  detectorId?: string;
}

interface DefineDetectorProps
  extends RouteComponentProps<DefineDetectorRouterProps> {
  isEdit: boolean;
  setStep?(stepNumber: number): void;
  initialValues?: DetectorDefinitionFormikValues;
  setInitialValues?(initialValues: DetectorDefinitionFormikValues): void;
  setModelConfigValues?(initialValues: ModelConfigurationFormikValues): void;
}

export const DefineDetector = (props: DefineDetectorProps) => {
  const core = React.useContext(CoreServicesContext) as CoreStart;
  const dispatch = useDispatch<Dispatch<APIAction>>();
  useHideSideNavBar(true, false);
  const detectorId: string = get(props, 'match.params.detectorId', '');
  const { detector, hasError } = useFetchDetectorInfo(detectorId);
  const [newIndexSelected, setNewIndexSelected] = useState<boolean>(false);

  // To handle backward compatibility, we need to pass some fields via
  // props to the subcomponents so they can render correctly
  //
  // oldFilterType: there used to only be one filter type per detector.
  // Now, the filter type is on a per-filter granularity, and each type
  // is stored within each filter in the filters array.
  //
  // oldFilterQuery: the custom filter query used to be stored here.
  // Now, the same field is repurposed in the new changes, but has a different meaning,
  // since it is a dynamically built query based on each individually-created filter.
  // See formikToFilterQuery() in public/pages/ReviewAndCreate/utils/helpers.ts for details.
  const oldFilterType = get(
    detector,
    'uiMetadata.filterType',
    undefined
  ) as FILTER_TYPES;
  const oldFilterQuery = get(detector, 'filterQuery', undefined);

  // Jump to top of page on first load
  useEffect(() => {
    scroll(0, 0);
  }, []);

  // Set breadcrumbs based on create / update
  useEffect(() => {
    const createOrEditBreadcrumb = props.isEdit
      ? BREADCRUMBS.EDIT_DETECTOR
      : BREADCRUMBS.CREATE_DETECTOR;
    let breadCrumbs = [
      BREADCRUMBS.ANOMALY_DETECTOR,
      BREADCRUMBS.DETECTORS,
      createOrEditBreadcrumb,
    ];
    if (detector && detector.name) {
      breadCrumbs.splice(2, 0, {
        text: detector.name,
        //@ts-ignore
        href: `#/detectors/${detectorId}`,
      });
    }
    core.chrome.setBreadcrumbs(breadCrumbs);
  });

  // If no detector found with ID, redirect it to list
  useEffect(() => {
    if (props.isEdit && hasError) {
      core.notifications.toasts.addDanger(
        'Unable to find the detector for editing'
      );
      props.history.push(`/detectors`);
    }
  }, [props.isEdit]);

  const handleValidateName = async (detectorName: string) => {
    if (isEmpty(detectorName)) {
      return 'Detector name cannot be empty';
    } else {
      const error = validateDetectorName(detectorName);
      if (error) {
        return error;
      }
      //TODO::Avoid making call if value is same
      const resp = await dispatch(matchDetector(detectorName));
      const match = get(resp, 'response.match', false);
      if (!match) {
        return undefined;
      }
      //If more than one detectors found, duplicate exists.
      if (!props.isEdit && match) {
        return 'Duplicate detector name';
      }
      // if it is in edit mode
      if (props.isEdit && detectorName !== detector?.name) {
        return 'Duplicate detector name';
      }
    }
  };

  const handleFormValidation = async (
    formikProps: FormikProps<DetectorDefinitionFormikValues>
  ) => {
    if (props.isEdit && detector.curState === DETECTOR_STATE.RUNNING) {
      core.notifications.toasts.addDanger(
        'Detector cannot be updated while it is running'
      );
    } else {
      formikProps.setSubmitting(true);
      formikProps.setFieldTouched('name');
      formikProps.setFieldTouched('description');
      formikProps.setFieldTouched('index');
      formikProps.setFieldTouched('resultIndex');
      formikProps.setFieldTouched('filters');
      formikProps.setFieldTouched('timeField');
      formikProps.setFieldTouched('interval');
      formikProps.setFieldTouched('windowDelay');
      formikProps.validateForm().then((errors) => {
        if (isEmpty(errors)) {
          if (props.isEdit) {
            const detectorToUpdate = formikToDetectorDefinition(
              formikProps.values,
              detector
            );
            handleUpdateDetector(detectorToUpdate);
          } else {
            optionallySaveValues(formikProps.values);
            //@ts-ignore
            props.setStep(2);
          }
        } else {
          // TODO: can add focus to all components or possibly customize error message too
          core.notifications.toasts.addDanger(
            'One or more input fields is invalid'
          );
        }
      });
    }
    formikProps.setSubmitting(false);
  };

  const handleUpdateDetector = async (detectorToUpdate: Detector) => {
    // If a new index was selected: clear any existing features and category fields
    const preparedDetector = newIndexSelected
      ? clearModelConfiguration(detectorToUpdate)
      : detectorToUpdate;
    dispatch(updateDetector(detectorId, preparedDetector))
      .then((response: any) => {
        core.notifications.toasts.addSuccess(
          `Detector updated: ${response.response.name}`
        );
        props.history.push(`/detectors/${detectorId}/configurations/`);
      })
      .catch((err: any) => {
        core.notifications.toasts.addDanger(
          prettifyErrorMessage(
            getErrorMessage(err, 'There was a problem updating the detector')
          )
        );
      });
  };

  const optionallySaveValues = (values: DetectorDefinitionFormikValues) => {
    if (props.setInitialValues) {
      props.setInitialValues(values);
    }
  };

  return (
    <Formik
      initialValues={
        props.initialValues
          ? props.initialValues
          : detectorDefinitionToFormik(detector)
      }
      enableReinitialize={true}
      onSubmit={() => {}}
      validateOnMount={props.isEdit ? false : true}
    >
      {(formikProps) => (
        <React.Fragment>
          <EuiPage
            style={{
              marginTop: '-24px',
            }}
          >
            <EuiPageBody>
              <EuiPageHeader>
                <EuiPageHeaderSection>
                  <EuiTitle size="l" data-test-subj="defineOrEditDetectorTitle">
                    <h1>
                      {props.isEdit
                        ? 'Edit detector settings'
                        : 'Define detector'}{' '}
                    </h1>
                  </EuiTitle>
                </EuiPageHeaderSection>
              </EuiPageHeader>
              <Fragment>
                <NameAndDescription
                  onValidateDetectorName={handleValidateName}
                />
                <EuiSpacer />
                <DataSource
                  formikProps={formikProps}
                  origIndex={
                    props.isEdit ? get(detector, 'indices.0', '') : null
                  }
                  isEdit={props.isEdit}
                  setModelConfigValues={props.setModelConfigValues}
                  setNewIndexSelected={setNewIndexSelected}
                  oldFilterType={oldFilterType}
                  oldFilterQuery={oldFilterQuery}
                />
                <EuiSpacer />
                <Timestamp formikProps={formikProps} />
                <EuiSpacer />
                <Settings />
                <EuiSpacer />
                <CustomResultIndex
                  isEdit={props.isEdit}
                  resultIndex={get(formikProps, 'values.resultIndex')}
                />
              </Fragment>
            </EuiPageBody>
          </EuiPage>

          <EuiSpacer size="xs" />
          <EuiFlexGroup
            alignItems="center"
            justifyContent="flexEnd"
            gutterSize="s"
            style={{ marginRight: '12px' }}
          >
            <EuiFlexItem grow={false}>
              <EuiButtonEmpty
                onClick={() => {
                  if (props.isEdit) {
                    props.history.push(
                      `/detectors/${detectorId}/configurations/`
                    );
                  } else {
                    props.history.push('/detectors');
                  }
                }}
              >
                Cancel
              </EuiButtonEmpty>
            </EuiFlexItem>
            <EuiFlexItem grow={false}>
              {props.isEdit ? (
                <EuiButton
                  type="submit"
                  fill={true}
                  data-test-subj="updateDetectorButton"
                  //@ts-ignore
                  onClick={() => {
                    handleFormValidation(formikProps);
                  }}
                >
                  Save changes
                </EuiButton>
              ) : (
                <EuiButton
                  type="submit"
                  iconSide="right"
                  iconType="arrowRight"
                  fill={true}
                  data-test-subj="defineDetectorNextButton"
                  isLoading={formikProps.isSubmitting}
                  //@ts-ignore
                  onClick={() => {
                    handleFormValidation(formikProps);
                  }}
                >
                  Next
                </EuiButton>
              )}
            </EuiFlexItem>
          </EuiFlexGroup>
        </React.Fragment>
      )}
    </Formik>
  );
};