<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import {
  GlButton,
  GlFormInput,
  GlLink,
  GlLoadingIcon,
  GlBadge,
  GlAlert,
  GlSprintf,
  GlDropdown,
  GlDropdownItem,
  GlDropdownDivider,
  GlIcon,
} from '@gitlab/ui';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { __, sprintf, n__ } from '~/locale';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import Stacktrace from './stacktrace.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { severityLevel, severityLevelVariant, errorStatus } from './constants';
import Tracking from '~/tracking';
import {
  trackClickErrorLinkToSentryOptions,
  trackErrorDetailsViewsOptions,
  trackErrorStatusUpdateOptions,
} from '../utils';

import query from '../queries/details.query.graphql';

const SENTRY_TIMEOUT = 10000;

export default {
  components: {
    GlButton,
    GlFormInput,
    GlLink,
    GlLoadingIcon,
    TooltipOnTruncate,
    GlIcon,
    Stacktrace,
    GlBadge,
    GlAlert,
    GlSprintf,
    GlDropdown,
    GlDropdownItem,
    GlDropdownDivider,
    TimeAgoTooltip,
  },
  directives: {
    TrackEvent: TrackEventDirective,
  },
  props: {
    issueUpdatePath: {
      type: String,
      required: true,
    },
    issueId: {
      type: String,
      required: true,
    },
    projectPath: {
      type: String,
      required: true,
    },
    issueStackTracePath: {
      type: String,
      required: true,
    },
    projectIssuesPath: {
      type: String,
      required: true,
    },
    csrfToken: {
      type: String,
      required: true,
    },
  },
  apollo: {
    error: {
      query,
      variables() {
        return {
          fullPath: this.projectPath,
          errorId: `gid://gitlab/Gitlab::ErrorTracking::DetailedError/${this.issueId}`,
        };
      },
      pollInterval: 2000,
      update: data => data.project.sentryErrors.detailedError,
      error: () => createFlash(__('Failed to load error details from Sentry.')),
      result(res) {
        if (res.data.project?.sentryErrors?.detailedError) {
          this.$apollo.queries.error.stopPolling();
          this.setStatus(this.error.status);
        } else {
          this.onNoApolloResult();
        }
      },
    },
  },
  data() {
    return {
      error: null,
      errorLoading: true,
      errorPollTimeout: 0,
      issueCreationInProgress: false,
      isAlertVisible: false,
      isStacktraceEmptyAlertVisible: true,
      closedIssueId: null,
    };
  },
  computed: {
    ...mapState('details', [
      'loadingStacktrace',
      'stacktraceData',
      'updatingResolveStatus',
      'updatingIgnoreStatus',
      'errorStatus',
    ]),
    ...mapGetters('details', ['stacktrace']),
    firstReleaseLink() {
      return `${this.error.externalBaseUrl}/releases/${this.error.firstReleaseVersion}`;
    },
    lastReleaseLink() {
      return `${this.error.externalBaseUrl}/releases/${this.error.lastReleaseVersion}`;
    },
    showStacktrace() {
      return Boolean(this.stacktrace?.length);
    },
    issueTitle() {
      return this.error.title;
    },
    issueDescription() {
      return sprintf(
        __(
          '%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}',
        ),
        {
          description: '# Error Details:\n',
          errorUrl: `${this.error.externalUrl}\n`,
          firstSeen: `\n${this.error.firstSeen}\n`,
          lastSeen: `${this.error.lastSeen}\n`,
          countLabel: n__('- Event', '- Events', this.error.count),
          count: `${this.error.count}\n`,
          userCountLabel: n__('- User', '- Users', this.error.userCount),
          userCount: `${this.error.userCount}\n`,
        },
        false,
      );
    },
    issueUpdateInProgress() {
      return (
        this.updatingIgnoreStatus || this.updatingResolveStatus || this.issueCreationInProgress
      );
    },
    errorLevel() {
      return sprintf(__('level: %{level}'), { level: this.error.tags.level });
    },
    errorSeverityVariant() {
      return (
        severityLevelVariant[this.error.tags.level] || severityLevelVariant[severityLevel.ERROR]
      );
    },
    ignoreBtnLabel() {
      return this.errorStatus !== errorStatus.IGNORED ? __('Ignore') : __('Undo ignore');
    },
    resolveBtnLabel() {
      return this.errorStatus !== errorStatus.RESOLVED ? __('Resolve') : __('Unresolve');
    },
    showEmptyStacktraceAlert() {
      return !this.loadingStacktrace && !this.showStacktrace && this.isStacktraceEmptyAlertVisible;
    },
  },
  watch: {
    error(val) {
      if (val) {
        this.errorLoading = false;
      }
    },
  },
  mounted() {
    this.trackPageViews();
    this.startPollingStacktrace(this.issueStackTracePath);
    this.errorPollTimeout = Date.now() + SENTRY_TIMEOUT;
    this.$apollo.queries.error.setOptions({
      fetchPolicy: 'cache-and-network',
    });
  },
  methods: {
    ...mapActions('details', [
      'startPollingStacktrace',
      'updateStatus',
      'setStatus',
      'updateResolveStatus',
      'updateIgnoreStatus',
    ]),
    trackClickErrorLinkToSentryOptions,
    createIssue() {
      this.issueCreationInProgress = true;
      this.$refs.sentryIssueForm.submit();
    },
    onIgnoreStatusUpdate() {
      const status =
        this.errorStatus === errorStatus.IGNORED ? errorStatus.UNRESOLVED : errorStatus.IGNORED;
      // eslint-disable-next-line promise/catch-or-return
      this.updateIgnoreStatus({ endpoint: this.issueUpdatePath, status }).then(() => {
        this.trackStatusUpdate(status);
      });
    },
    onResolveStatusUpdate() {
      const status =
        this.errorStatus === errorStatus.RESOLVED ? errorStatus.UNRESOLVED : errorStatus.RESOLVED;

      // eslint-disable-next-line promise/catch-or-return
      this.updateResolveStatus({ endpoint: this.issueUpdatePath, status }).then(res => {
        this.closedIssueId = res.closed_issue_iid;
        if (this.closedIssueId) {
          this.isAlertVisible = true;
        }
        this.trackStatusUpdate(status);
      });
    },
    onNoApolloResult() {
      if (Date.now() > this.errorPollTimeout) {
        this.$apollo.queries.error.stopPolling();
        this.errorLoading = false;
        createFlash(__('Could not connect to Sentry. Refresh the page to try again.'), 'warning');
      }
    },
    trackPageViews() {
      const { category, action } = trackErrorDetailsViewsOptions;
      Tracking.event(category, action);
    },
    trackStatusUpdate(status) {
      const { category, action } = trackErrorStatusUpdateOptions(status);
      Tracking.event(category, action);
    },
  },
};
</script>

<template>
  <div>
    <div v-if="errorLoading" class="py-3">
      <gl-loading-icon size="lg" />
    </div>
    <div v-else-if="error" class="error-details">
      <gl-alert v-if="isAlertVisible" @dismiss="isAlertVisible = false">
        <gl-sprintf
          :message="
            __('The associated issue #%{issueId} has been closed as the error is now resolved.')
          "
        >
          <template #issueId>
            <span>{{ closedIssueId }}</span>
          </template>
        </gl-sprintf>
      </gl-alert>

      <gl-alert v-if="showEmptyStacktraceAlert" @dismiss="isStacktraceEmptyAlertVisible = false">
        {{ __('No stack trace for this error') }}
      </gl-alert>

      <div class="error-details-header d-flex py-2 justify-content-between">
        <div
          v-if="!loadingStacktrace && stacktrace"
          class="error-details-meta my-auto"
          data-qa-selector="reported_text"
        >
          <gl-sprintf :message="__('Reported %{timeAgo} by %{reportedBy}')">
            <template #reportedBy>
              <strong class="error-details-meta-culprit">{{ error.culprit }}</strong>
            </template>
            <template #timeAgo>
              <time-ago-tooltip :time="stacktraceData.date_received" />
            </template>
          </gl-sprintf>
        </div>
        <div class="error-details-actions">
          <div class="d-inline-flex bv-d-sm-down-none">
            <gl-button
              :loading="updatingIgnoreStatus"
              data-testid="update-ignore-status-btn"
              @click="onIgnoreStatusUpdate"
            >
              {{ ignoreBtnLabel }}
            </gl-button>
            <gl-button
              class="ml-2"
              category="secondary"
              variant="info"
              :loading="updatingResolveStatus"
              data-testid="update-resolve-status-btn"
              @click="onResolveStatusUpdate"
            >
              {{ resolveBtnLabel }}
            </gl-button>
            <gl-button
              v-if="error.gitlabIssuePath"
              class="ml-2"
              data-testid="view_issue_button"
              :href="error.gitlabIssuePath"
              variant="success"
            >
              {{ __('View issue') }}
            </gl-button>
            <form
              ref="sentryIssueForm"
              :action="projectIssuesPath"
              method="POST"
              class="d-inline-block ml-2"
            >
              <gl-form-input class="hidden" name="issue[title]" :value="issueTitle" />
              <input name="issue[description]" :value="issueDescription" type="hidden" />
              <gl-form-input
                :value="error.sentryId"
                class="hidden"
                name="issue[sentry_issue_attributes][sentry_issue_identifier]"
              />
              <gl-form-input :value="csrfToken" class="hidden" name="authenticity_token" />
              <gl-button
                v-if="!error.gitlabIssuePath"
                category="primary"
                variant="success"
                :loading="issueCreationInProgress"
                data-qa-selector="create_issue_button"
                @click="createIssue"
              >
                {{ __('Create issue') }}
              </gl-button>
            </form>
          </div>
          <gl-dropdown
            text="Options"
            class="error-details-options d-md-none"
            right
            :disabled="issueUpdateInProgress"
          >
            <gl-dropdown-item
              data-qa-selector="update_ignore_status_button"
              @click="onIgnoreStatusUpdate"
              >{{ ignoreBtnLabel }}</gl-dropdown-item
            >
            <gl-dropdown-item
              data-qa-selector="update_resolve_status_button"
              @click="onResolveStatusUpdate"
              >{{ resolveBtnLabel }}</gl-dropdown-item
            >
            <gl-dropdown-divider />
            <gl-dropdown-item
              v-if="error.gitlabIssuePath"
              data-qa-selector="view_issue_button"
              :href="error.gitlabIssuePath"
              variant="success"
              >{{ __('View issue') }}</gl-dropdown-item
            >
            <gl-dropdown-item
              v-if="!error.gitlabIssuePath"
              :loading="issueCreationInProgress"
              data-qa-selector="create_issue_button"
              @click="createIssue"
              >{{ __('Create issue') }}</gl-dropdown-item
            >
          </gl-dropdown>
        </div>
      </div>
      <div>
        <tooltip-on-truncate :title="error.title" truncate-target="child" placement="top">
          <h2 class="text-truncate">{{ error.title }}</h2>
        </tooltip-on-truncate>
        <template v-if="error.tags">
          <gl-badge v-if="error.tags.level" :variant="errorSeverityVariant" class="mr-2">
            {{ errorLevel }}
          </gl-badge>
          <gl-badge v-if="error.tags.logger" variant="muted">{{ error.tags.logger }} </gl-badge>
        </template>
        <ul>
          <li v-if="error.gitlabCommit">
            <strong class="bold">{{ __('GitLab commit') }}:</strong>
            <gl-link :href="error.gitlabCommitPath">
              <span>{{ error.gitlabCommit.substr(0, 10) }}</span>
            </gl-link>
          </li>
          <li v-if="error.gitlabIssuePath">
            <strong class="bold">{{ __('GitLab Issue') }}:</strong>
            <gl-link :href="error.gitlabIssuePath">
              <span>{{ error.gitlabIssuePath }}</span>
            </gl-link>
          </li>
          <li>
            <strong class="bold">{{ __('Sentry event') }}:</strong>
            <gl-link
              v-track-event="trackClickErrorLinkToSentryOptions(error.externalUrl)"
              :href="error.externalUrl"
              target="_blank"
              data-testid="external-url-link"
            >
              <span class="text-truncate">{{ error.externalUrl }}</span>
              <gl-icon name="external-link" class="ml-1 flex-shrink-0" />
            </gl-link>
          </li>
          <li v-if="error.firstReleaseVersion">
            <strong class="bold">{{ __('First seen') }}:</strong>
            <time-ago-tooltip :time="error.firstSeen" />
            <gl-link :href="firstReleaseLink" target="_blank">
              <span>{{ __('Release') }}: {{ error.firstReleaseVersion }}</span>
            </gl-link>
          </li>
          <li v-if="error.lastReleaseVersion">
            <strong class="bold">{{ __('Last seen') }}:</strong>
            <time-ago-tooltip :time="error.lastSeen" />
            <gl-link :href="lastReleaseLink" target="_blank">
              <span>{{ __('Release') }}: {{ error.lastReleaseVersion }}</span>
            </gl-link>
          </li>
          <li>
            <strong class="bold">{{ __('Events') }}:</strong>
            <span>{{ error.count }}</span>
          </li>
          <li>
            <strong class="bold">{{ __('Users') }}:</strong>
            <span>{{ error.userCount }}</span>
          </li>
        </ul>

        <div v-if="loadingStacktrace" class="py-3">
          <gl-loading-icon size="lg" />
        </div>

        <template v-else-if="showStacktrace">
          <h3 class="my-4">{{ __('Stack trace') }}</h3>
          <stacktrace :entries="stacktrace" />
        </template>
      </div>
    </div>
  </div>
</template>
