/*
 * Copyright OpenSearch Contributors
 * SPDX-License-Identifier: Apache-2.0
 */

import React, { Component } from 'react';
import {
  EuiAccordion,
  EuiBadge,
  EuiBadgeGroup,
  EuiButton,
  EuiButtonIcon,
  EuiCodeBlock,
  EuiFlexGroup,
  EuiFlexItem,
  EuiFlyout,
  EuiFlyoutBody,
  EuiFlyoutHeader,
  EuiFormRow,
  EuiHorizontalRule,
  EuiLink,
  EuiModal,
  EuiModalBody,
  EuiModalHeader,
  EuiModalHeaderTitle,
  EuiPanel,
  EuiSpacer,
  EuiText,
  EuiTitle,
  EuiIcon,
  EuiTabs,
  EuiTab,
  EuiLoadingContent,
} from '@elastic/eui';
import { capitalizeFirstLetter, renderTime } from '../../../utils/helpers';
import { DEFAULT_EMPTY_DATA, ROUTES } from '../../../utils/constants';
import { Query } from '../models/interfaces';
import { RuleViewerFlyout } from '../../Rules/components/RuleViewerFlyout/RuleViewerFlyout';
import { RuleSource } from '../../../../server/models/interfaces';
import { OpenSearchService, IndexPatternsService, CorrelationService } from '../../../services';
import { RuleTableItem } from '../../Rules/utils/helpers';
import { CreateIndexPatternForm } from './CreateIndexPatternForm';
import { FindingItemType } from '../containers/Findings/Findings';
import { CorrelationFinding, RuleItemInfoBase } from '../../../../types';
import { FindingFlyoutTabId, FindingFlyoutTabs } from '../utils/constants';
import { DataStore } from '../../../store/DataStore';
import { CorrelationsTable } from './CorrelationsTable/CorrelationsTable';

export interface FindingDetailsFlyoutBaseProps {
  finding: FindingItemType;
  findings: FindingItemType[];
  shouldLoadAllFindings: boolean;
  backButton?: React.ReactNode;
}

export interface FindingDetailsFlyoutProps extends FindingDetailsFlyoutBaseProps {
  opensearchService: OpenSearchService;
  indexPatternsService: IndexPatternsService;
  correlationService: CorrelationService;
  history: History;
}

interface FindingDetailsFlyoutState {
  loading: boolean;
  ruleViewerFlyoutData: RuleTableItem | null;
  indexPatternId?: string;
  isCreateIndexPatternModalVisible: boolean;
  selectedTab: { id: string; content: React.ReactNode | null };
  correlatedFindings: CorrelationFinding[];
  allRules: { [id: string]: RuleSource };
  isDocumentLoading: boolean;
  areCorrelationsLoading: boolean;
}

export default class FindingDetailsFlyout extends Component<
  FindingDetailsFlyoutProps,
  FindingDetailsFlyoutState
> {
  constructor(props: FindingDetailsFlyoutProps) {
    super(props);
    this.state = {
      loading: false,
      ruleViewerFlyoutData: null,
      isCreateIndexPatternModalVisible: false,
      selectedTab: {
        id: FindingFlyoutTabId.DETAILS,
        content: (
          <>
            <EuiTitle size={'s'}>
              <h3>Rule details</h3>
            </EuiTitle>
            <EuiSpacer size={'m'} />
            <EuiLoadingContent lines={4} />
          </>
        ),
      },
      correlatedFindings: [],
      isDocumentLoading: true,
      areCorrelationsLoading: true,
      allRules: {},
    };
  }

  getCorrelations = async () => {
    const { id, detector } = this.props.finding;
    let allFindings = this.props.findings;
    if (this.props.shouldLoadAllFindings) {
      // if findings come from the alerts fly-out, we need to get all the findings to match those with the correlations
      allFindings = await DataStore.findings.getAllFindings();
    }

    DataStore.correlations.getCorrelationRules().then((correlationRules) => {
      DataStore.correlations
        .getCorrelatedFindings(id, detector._source?.detector_type)
        .then((findings) => {
          if (findings?.correlatedFindings.length) {
            let correlatedFindings: any[] = [];
            findings.correlatedFindings.map((finding: CorrelationFinding) => {
              allFindings.map((item: FindingItemType) => {
                if (finding.id === item.id) {
                  correlatedFindings.push({
                    ...finding,
                    correlationRule: correlationRules.find(
                      (rule) => finding.rules?.indexOf(rule.id) !== -1
                    ),
                  });
                }
              });
            });
            this.setState({ correlatedFindings });
          }
        })
        .finally(() => {
          this.setState({
            areCorrelationsLoading: false,
          });
        });
    });
  };

  componentDidMount(): void {
    this.getIndexPatternId()
      .then((patternId) => {
        if (patternId) {
          this.setState({ indexPatternId: patternId });
        }
      })
      .finally(() => {
        this.setState({ isDocumentLoading: false });
      });

    this.getCorrelations();

    DataStore.rules.getAllRules().then((rules) => {
      const allRules: { [id: string]: RuleSource } = {};
      rules.forEach((hit) => (allRules[hit._id] = hit._source));
      this.setState({ allRules }, () => {
        this.setState({
          selectedTab: {
            id: FindingFlyoutTabId.DETAILS,
            content: this.getTabContent(FindingFlyoutTabId.DETAILS),
          },
        });
      });
    });
  }

  renderTags = () => {
    const { finding } = this.props;
    const tags = finding.queries[0].tags || [];
    return (
      tags && (
        <EuiBadgeGroup gutterSize={'s'}>
          {tags.map((tag, key) => (
            <EuiBadge key={key}>{tag}</EuiBadge>
          ))}
        </EuiBadgeGroup>
      )
    );
  };

  showRuleDetails = (fullRule: any, ruleId: string) => {
    this.setState({
      ...this.state,
      ruleViewerFlyoutData: {
        ruleId: ruleId,
        title: fullRule.title,
        level: fullRule.level,
        category: fullRule.category,
        description: fullRule.description,
        source: fullRule.source,
        ruleInfo: {
          _source: fullRule,
          prePackaged: fullRule.prePackaged,
        } as RuleItemInfoBase,
      },
    });
  };

  hideRuleDetails = () => {
    this.setState({ ...this.state, ruleViewerFlyoutData: null });
  };

  renderRuleDetails = (rules: Query[] = []) => {
    const { allRules = {} } = this.state;
    return rules.map((rule, key) => {
      const fullRule = allRules[rule.id];
      const severity = capitalizeFirstLetter(fullRule.level);
      return (
        <div key={key}>
          <EuiAccordion
            id={`${key}`}
            buttonClassName="euiAccordionForm__button"
            buttonContent={
              <div data-test-subj={'finding-details-flyout-rule-accordion-button'}>
                <EuiText size={'s'}>{fullRule.title}</EuiText>
                <EuiText size={'s'} color={'subdued'}>
                  Severity: {severity}
                </EuiText>
              </div>
            }
            initialIsOpen={rules.length === 1}
            data-test-subj={`finding-details-flyout-rule-accordion-${key}`}
          >
            <EuiPanel color="subdued">
              <EuiFlexGroup>
                <EuiFlexItem>
                  <EuiFormRow label={'Rule name'}>
                    <EuiLink
                      onClick={() => this.showRuleDetails(fullRule, rule.id)}
                      data-test-subj={`finding-details-flyout-${fullRule.title}-details`}
                    >
                      {fullRule.title || DEFAULT_EMPTY_DATA}
                    </EuiLink>
                  </EuiFormRow>
                </EuiFlexItem>

                <EuiFlexItem>
                  <EuiFormRow
                    label={'Rule severity'}
                    data-test-subj={'finding-details-flyout-rule-severity'}
                  >
                    <EuiText>{severity || DEFAULT_EMPTY_DATA}</EuiText>
                  </EuiFormRow>
                </EuiFlexItem>

                <EuiFlexItem>
                  <EuiFormRow
                    label={'Log type'}
                    data-test-subj={'finding-details-flyout-rule-category'}
                  >
                    <EuiText>
                      {capitalizeFirstLetter(fullRule.category) || DEFAULT_EMPTY_DATA}
                    </EuiText>
                  </EuiFormRow>
                </EuiFlexItem>
              </EuiFlexGroup>

              <EuiSpacer size={'m'} />

              <EuiFormRow
                label={'Description'}
                data-test-subj={'finding-details-flyout-rule-description'}
              >
                <EuiText>{fullRule.description || DEFAULT_EMPTY_DATA}</EuiText>
              </EuiFormRow>

              <EuiSpacer size={'m'} />

              <EuiFormRow label={'Tags'} data-test-subj={'finding-details-flyout-rule-tags'}>
                <EuiText>{this.renderTags() || DEFAULT_EMPTY_DATA}</EuiText>
              </EuiFormRow>
            </EuiPanel>
          </EuiAccordion>
          {rules.length > 1 && <EuiHorizontalRule margin={'xs'} />}
        </div>
      );
    });
  };

  getIndexPatternId = async () => {
    const indexPatterns = await this.props.opensearchService.getIndexPatterns();
    const {
      finding: { index },
    } = this.props;
    let patternId;
    indexPatterns.map((pattern) => {
      const patternName = pattern.attributes.title.replaceAll('*', '.*');
      const patternRegex = new RegExp(patternName);
      if (index.match(patternRegex)) {
        patternId = pattern.id;
      }
    });

    return patternId;
  };

  renderFindingDocuments(isDocumentLoading: boolean) {
    const {
      finding: { index, document_list, related_doc_ids },
    } = this.props;
    const documents = document_list;
    const docId = related_doc_ids[0];
    const matchedDocuments = documents.filter((doc) => doc.id === docId);
    const document = matchedDocuments.length > 0 ? matchedDocuments[0].document : '';
    const { indexPatternId } = this.state;

    return (
      <>
        <EuiFlexGroup justifyContent="spaceBetween">
          <EuiFlexItem>
            <EuiTitle size={'s'}>
              <h3>Documents</h3>
            </EuiTitle>
          </EuiFlexItem>
          <EuiFlexItem grow={false}>
            <EuiButton
              isLoading={isDocumentLoading}
              data-test-subj={'finding-details-flyout-view-surrounding-documents'}
              onClick={() => {
                if (indexPatternId) {
                  window.open(
                    `discover#/context/${indexPatternId}/${related_doc_ids[0]}`,
                    '_blank'
                  );
                } else {
                  this.setState({ ...this.state, isCreateIndexPatternModalVisible: true });
                }
              }}
            >
              View surrounding documents
              <EuiIcon type={'popout'} />
            </EuiButton>
          </EuiFlexItem>
        </EuiFlexGroup>

        <EuiSpacer size={'s'} />

        <EuiFlexGroup>
          <EuiFlexItem>
            <EuiFormRow
              label={'Document ID'}
              data-test-subj={'finding-details-flyout-rule-document-id'}
            >
              <EuiText>{docId || DEFAULT_EMPTY_DATA}</EuiText>
            </EuiFormRow>
          </EuiFlexItem>

          <EuiFlexItem>
            <EuiFormRow
              label={'Index'}
              data-test-subj={'finding-details-flyout-rule-document-index'}
            >
              <EuiText>{index || DEFAULT_EMPTY_DATA}</EuiText>
            </EuiFormRow>
          </EuiFlexItem>
        </EuiFlexGroup>

        <EuiSpacer size={'m'} />

        <EuiFormRow fullWidth={true}>
          <EuiCodeBlock
            language="json"
            isCopyable
            data-test-subj={'finding-details-flyout-rule-document'}
          >
            {JSON.stringify(JSON.parse(document), null, 2)}
          </EuiCodeBlock>
        </EuiFormRow>
      </>
    );
  }

  createIndexPatternModal() {
    const {
      finding: { related_doc_ids },
    } = this.props;
    if (this.state.isCreateIndexPatternModalVisible) {
      return (
        <EuiModal
          style={{ width: 800 }}
          onClose={() => this.setState({ ...this.state, isCreateIndexPatternModalVisible: false })}
        >
          <EuiModalHeader>
            <EuiModalHeaderTitle>
              <h1>Create index pattern to view documents</h1>
            </EuiModalHeaderTitle>
          </EuiModalHeader>

          <EuiModalBody>
            <CreateIndexPatternForm
              indexPatternsService={this.props.indexPatternsService}
              initialValue={{
                name: this.props.finding.detector._source.inputs[0].detector_input.indices[0] + '*',
              }}
              close={() =>
                this.setState({ ...this.state, isCreateIndexPatternModalVisible: false })
              }
              created={(indexPatternId) => {
                this.setState({
                  ...this.state,
                  indexPatternId,
                  isCreateIndexPatternModalVisible: false,
                });
                window.open(`discover#/context/${indexPatternId}/${related_doc_ids[0]}`, '_blank');
              }}
            ></CreateIndexPatternForm>
          </EuiModalBody>
        </EuiModal>
      );
    }
  }

  private getTabContent(tabId: FindingFlyoutTabId, isDocumentLoading = false) {
    switch (tabId) {
      case FindingFlyoutTabId.CORRELATIONS:
        const logTypes = new Set<string>();
        const ruleSeverity = new Set<string>();
        Object.values(this.state.allRules).forEach((rule) => {
          logTypes.add(rule.category);
          ruleSeverity.add(rule.level);
        });

        return (
          <CorrelationsTable
            finding={this.props.finding}
            correlatedFindings={this.state.correlatedFindings}
            history={this.props.history}
            isLoading={this.state.areCorrelationsLoading}
            filterOptions={{
              logTypes,
              ruleSeverity,
            }}
          />
        );
      case FindingFlyoutTabId.DETAILS:
      default:
        return this.createFindingDetails(isDocumentLoading);
    }
  }

  private createFindingDetails(isDocumentLoading: boolean) {
    const {
      finding: { queries },
    } = this.props;

    return (
      <>
        <EuiTitle size={'s'}>
          <h3>Rule details</h3>
        </EuiTitle>
        <EuiSpacer size={'m'} />
        {this.renderRuleDetails(queries)}
        <EuiSpacer size="l" />
        {this.renderFindingDocuments(isDocumentLoading)}
      </>
    );
  }

  render() {
    const { backButton } = this.props;
    const {
      finding: {
        id,
        detector: {
          _id,
          _source: { name },
        },
        timestamp,
      },
    } = this.props;
    const { isDocumentLoading } = this.state;
    return (
      <EuiFlyout
        onClose={DataStore.findings.closeFlyout}
        ownFocus={true}
        size={'m'}
        hideCloseButton
        data-test-subj={'finding-details-flyout'}
      >
        {this.state.ruleViewerFlyoutData && (
          <RuleViewerFlyout
            hideFlyout={this.hideRuleDetails}
            ruleTableItem={this.state.ruleViewerFlyoutData}
          />
        )}
        {this.createIndexPatternModal()}
        <EuiFlyoutHeader>
          <EuiFlexGroup justifyContent="flexStart" alignItems="center">
            <EuiFlexItem>
              <EuiFlexGroup alignItems="center">
                {!!backButton ? <EuiFlexItem grow={false}>{backButton}</EuiFlexItem> : null}
                <EuiFlexItem>
                  <EuiTitle size={'m'}>
                    <h3>Finding details</h3>
                  </EuiTitle>
                </EuiFlexItem>
              </EuiFlexGroup>
            </EuiFlexItem>
            <EuiFlexItem grow={false}>
              <EuiButtonIcon
                aria-label="close"
                iconType="cross"
                display="empty"
                iconSize="m"
                onClick={DataStore.findings.closeFlyout}
                data-test-subj={`close-finding-details-flyout`}
              />
            </EuiFlexItem>
          </EuiFlexGroup>
        </EuiFlyoutHeader>
        <EuiFlyoutBody>
          <EuiFlexGroup>
            <EuiFlexItem>
              <EuiFormRow label={'Finding ID'}>
                <EuiText data-test-subj={'finding-details-flyout-finding-id'}>
                  {id || DEFAULT_EMPTY_DATA}
                </EuiText>
              </EuiFormRow>
            </EuiFlexItem>

            <EuiFlexItem>
              <EuiFormRow
                label={'Finding time'}
                data-test-subj={'finding-details-flyout-timestamp'}
              >
                <EuiText>{renderTime(timestamp) || DEFAULT_EMPTY_DATA}</EuiText>
              </EuiFormRow>
            </EuiFlexItem>

            <EuiFlexItem>
              <EuiFormRow label={'Detector'}>
                <EuiLink
                  href={`#${ROUTES.DETECTOR_DETAILS}/${_id}`}
                  target={'_blank'}
                  data-test-subj={'finding-details-flyout-detector-link'}
                >
                  {name || DEFAULT_EMPTY_DATA}
                </EuiLink>
              </EuiFormRow>
            </EuiFlexItem>
          </EuiFlexGroup>

          <EuiSpacer size={'m'} />
          <EuiTabs>
            {FindingFlyoutTabs.map((tab) => {
              return (
                <EuiTab
                  key={tab.id}
                  isSelected={tab.id === this.state.selectedTab.id}
                  onClick={() => {
                    this.setState({
                      selectedTab: {
                        id: tab.id,
                        content: this.getTabContent(tab.id, isDocumentLoading),
                      },
                    });
                  }}
                >
                  {tab.id === 'Correlations' ? (
                    <>
                      {tab.name} (
                      {this.state.areCorrelationsLoading
                        ? DEFAULT_EMPTY_DATA
                        : this.state.correlatedFindings.length}
                      )
                    </>
                  ) : (
                    tab.name
                  )}
                </EuiTab>
              );
            })}
          </EuiTabs>
          <EuiSpacer />
          {this.state.selectedTab.content}
        </EuiFlyoutBody>
      </EuiFlyout>
    );
  }
}