<script>
import { GlFriendlyWrap, GlLink, GlBadge } from '@gitlab/ui';
import { REPORT_TYPES } from 'ee/security_dashboard/store/constants';
import { SUPPORTING_MESSAGE_TYPES } from 'ee/vulnerabilities/constants';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { s__, sprintf } from '~/locale';
import CodeBlock from '~/vue_shared/components/code_block.vue';
import getFileLocation from '../store/utils/get_file_location';
import { bodyWithFallBack } from './helpers';
import SeverityBadge from './severity_badge.vue';
import VulnerabilityDetail from './vulnerability_detail.vue';

export default {
  name: 'VulnerabilityDetails',
  components: { CodeBlock, GlFriendlyWrap, SeverityBadge, VulnerabilityDetail, GlLink, GlBadge },
  props: { vulnerability: { type: Object, required: true } },
  computed: {
    url() {
      return this.vulnerability.request?.url || getFileLocation(this.vulnLocation);
    },
    file() {
      const file = this.vulnerability?.location?.file;
      if (!file) {
        return null;
      }
      let lineSuffix = '';
      const { start_line: startLine, end_line: endLine } = this.vulnLocation;
      if (startLine) {
        lineSuffix += `:${startLine}`;
        if (endLine && startLine !== endLine) {
          lineSuffix += `-${endLine}`;
        }
      }
      return `${file}${lineSuffix}`;
    },
    identifiers() {
      return this.asNonEmptyListOrNull(this.vulnerability.identifiers);
    },
    vulnLocation() {
      return this.vulnerability.location;
    },
    crashAddress() {
      return this.vulnLocation?.crash_address;
    },
    crashType() {
      return this.vulnLocation?.crash_type;
    },
    crashState() {
      return this.vulnLocation?.crash_state;
    },
    className() {
      return this.vulnLocation?.class;
    },
    methodName() {
      return this.vulnLocation?.method;
    },
    image() {
      return this.vulnLocation?.image;
    },
    namespace() {
      return this.vulnLocation?.operating_system;
    },
    assets() {
      return this.asNonEmptyListOrNull(this.vulnerability.assets);
    },
    links() {
      return this.asNonEmptyListOrNull(this.vulnerability.links);
    },
    assertion() {
      return this.vulnerability.evidenceSource?.name;
    },
    recordedMessage() {
      return this.vulnerability.supporting_messages?.find(
        (msg) => msg.name === SUPPORTING_MESSAGE_TYPES.RECORDED,
      )?.response;
    },
    constructedRequest() {
      const { request } = this.vulnerability;
      return request ? this.constructRequest(request) : '';
    },
    constructedResponse() {
      const { response } = this.vulnerability;
      return response ? this.constructResponse(response) : '';
    },
    constructedRecordedResponse() {
      return this.recordedMessage ? this.constructResponse(this.recordedMessage) : '';
    },
    responseStatusCode() {
      return this.vulnerability.response?.status_code;
    },
    scannerType() {
      return REPORT_TYPES[this.vulnerability.report_type];
    },
    scannerUrl() {
      return this.vulnerability.scanner?.url || '';
    },
    scannerVersion() {
      return this.vulnerability.scanner?.version;
    },
    scannerName() {
      return this.vulnerability.scanner?.name;
    },
    scannerProvider() {
      if (!this.scannerVersion) {
        return this.scannerName;
      }
      return sprintf(s__('Vulnerability|%{scannerName} (version %{scannerVersion})'), {
        scannerName: this.scannerName,
        scannerVersion: this.scannerVersion,
      });
    },
    stacktraceSnippet() {
      return this.vulnLocation?.stacktrace_snippet;
    },
    hasRequest() {
      return Boolean(this.constructedRequest);
    },
    hasResponse() {
      return Boolean(this.constructedResponse);
    },
    hasRecordedResponse() {
      return Boolean(this.constructedRecordedResponse);
    },
  },
  methods: {
    getHeadersAsCodeBlockLines(headers) {
      return Array.isArray(headers)
        ? headers.map(({ name, value }) => `${name}: ${value}`).join('\n')
        : '';
    },
    hasMoreValues(index, values) {
      return index < values.length - 1;
    },
    asNonEmptyListOrNull(list) {
      return list?.length > 0 ? list : null;
    },
    headersToFormattedString(headers = []) {
      return headers.map(({ name, value }) => `${name}: ${value}`).join('\n');
    },
    constructResponse(response) {
      const { body, statusCode, reasonPhrase = '', headers = [] } = convertObjectPropsToCamelCase(
        response,
      );
      const headerLines = this.getHeadersAsCodeBlockLines(headers);

      return statusCode && headerLines
        ? [`${statusCode} ${reasonPhrase}\n`, headerLines, '\n\n', bodyWithFallBack(body)].join('')
        : '';
    },
    constructRequest(request) {
      const { body, method, url, headers = [] } = request;
      const headerLines = this.getHeadersAsCodeBlockLines(headers);

      return method && url && headerLines
        ? [`${method} ${url}\n`, headerLines, '\n\n', bodyWithFallBack(body)].join('')
        : '';
    },
  },
};
</script>
<template>
  <div class="border-white mb-0 px-3">
    <vulnerability-detail v-if="vulnerability.state" :label="s__('Vulnerability|Status')">
      <gl-badge variant="warning" class="text-capitalize">{{ vulnerability.state }}</gl-badge>
    </vulnerability-detail>
    <vulnerability-detail
      v-if="vulnerability.description"
      :label="s__('Vulnerability|Description')"
    >
      <gl-friendly-wrap :text="vulnerability.description" />
    </vulnerability-detail>
    <vulnerability-detail v-if="vulnerability.project" :label="s__('Vulnerability|Project')">
      <gl-link ref="projectLink" :href="vulnerability.project.full_path" target="_blank">
        <gl-friendly-wrap :text="vulnerability.project.full_name" />
      </gl-link>
    </vulnerability-detail>
    <vulnerability-detail v-if="methodName" :label="s__('Vulnerability|Method')">
      <gl-friendly-wrap :text="methodName" />
    </vulnerability-detail>
    <vulnerability-detail v-if="url" :label="__('URL')">
      <gl-link ref="urlLink" :href="url" target="_blank">
        <gl-friendly-wrap :text="url" />
      </gl-link>
    </vulnerability-detail>
    <vulnerability-detail v-if="hasRequest" :label="s__('Vulnerability|Request')">
      <code-block ref="request" :code="constructedRequest" max-height="225px" />
    </vulnerability-detail>
    <vulnerability-detail
      v-if="hasRecordedResponse"
      :label="s__('Vulnerability|Unmodified Response')"
    >
      <code-block ref="recordedResponse" :code="constructedRecordedResponse" max-height="225px" />
    </vulnerability-detail>
    <vulnerability-detail v-if="hasResponse" :label="s__('Vulnerability|Actual Response')">
      <code-block ref="unmodifiedResponse" :code="constructedResponse" max-height="225px" />
    </vulnerability-detail>
    <vulnerability-detail v-if="file" :label="s__('Vulnerability|File')">
      <gl-link
        v-if="vulnerability.blob_path"
        ref="fileLink"
        :href="vulnerability.blob_path"
        target="_blank"
      >
        <gl-friendly-wrap :text="file" />
      </gl-link>
      <gl-friendly-wrap v-else :text="file" />
    </vulnerability-detail>
    <vulnerability-detail v-if="vulnerability.evidence" :label="s__('Vulnerability|Evidence')">
      <gl-friendly-wrap :text="vulnerability.evidence" />
    </vulnerability-detail>
    <vulnerability-detail v-if="crashAddress" :label="s__('Vulnerability|Crash address')">
      <span ref="crashAddress"> {{ crashAddress }} </span>
    </vulnerability-detail>
    <vulnerability-detail v-if="crashType" :label="s__('Vulnerability|Crash type')">
      <span ref="crashType"> {{ crashType }} </span>
    </vulnerability-detail>
    <vulnerability-detail v-if="crashState" :label="s__('Vulnerability|Crash state')">
      <span ref="crashState"> {{ crashState }} </span>
    </vulnerability-detail>
    <vulnerability-detail v-if="stacktraceSnippet" :label="s__('Stacktrace snippet')">
      <code-block ref="stacktraceSnippet" :code="stacktraceSnippet" max-height="225px" />
    </vulnerability-detail>
    <vulnerability-detail v-if="identifiers" :label="s__('Vulnerability|Identifiers')">
      <span v-for="(identifier, i) in identifiers" :key="i">
        <gl-link
          v-if="identifier.url"
          ref="identifiersLink"
          class="gl-word-break-all"
          :href="identifier.url"
          target="_blank"
          rel="nofollow"
        >
          {{ identifier.name }}
        </gl-link>
        <span v-else> {{ identifier.name }} </span>
        <span v-if="hasMoreValues(i, identifiers)">,&nbsp;</span>
      </span>
    </vulnerability-detail>
    <vulnerability-detail v-if="vulnerability.severity" :label="s__('Vulnerability|Severity')">
      <severity-badge :severity="vulnerability.severity" class="d-inline" />
    </vulnerability-detail>
    <vulnerability-detail v-if="vulnerability.report_type" :label="s__('Vulnerability|Scanner')">
      <gl-friendly-wrap :text="scannerType" />
    </vulnerability-detail>
    <vulnerability-detail v-if="scannerProvider" :label="s__('Vulnerability|Scanner Provider')">
      <gl-link v-if="scannerUrl" ref="scannerLink" :href="scannerUrl" target="_blank">
        <gl-friendly-wrap :text="scannerProvider" />
      </gl-link>
      <gl-friendly-wrap v-else :text="scannerProvider" />
    </vulnerability-detail>
    <vulnerability-detail v-if="className" :label="s__('Vulnerability|Class')">
      <gl-friendly-wrap :text="className" />
    </vulnerability-detail>
    <vulnerability-detail v-if="image" :label="s__('Vulnerability|Image')">
      <gl-friendly-wrap :text="image" />
    </vulnerability-detail>
    <vulnerability-detail v-if="namespace" :label="s__('Vulnerability|Namespace')">
      <gl-friendly-wrap :text="namespace" />
    </vulnerability-detail>
    <vulnerability-detail v-if="links" :label="s__('Vulnerability|Links')">
      <span v-for="(link, i) in links" :key="i">
        <gl-link ref="linksLink" class="gl-word-break-all" :href="link.url" target="_blank">
          {{ link.value || link.url }}
        </gl-link>
        <span v-if="hasMoreValues(i, links)">,&nbsp;</span>
      </span>
    </vulnerability-detail>
    <vulnerability-detail v-if="assets" :label="s__('Vulnerability|Reproduction Assets')">
      <span v-for="(asset, i) in assets" :key="i">
        <gl-link
          v-if="asset.url"
          ref="assetsLink"
          class="gl-word-break-all"
          :href="asset.url"
          target="_blank"
          rel="nofollow"
        >
          {{ s__('Vulnerability|Download') }} {{ asset.name }}
        </gl-link>
        <span v-else> {{ asset.name }} </span>
        <span v-if="hasMoreValues(i, assets)">,&nbsp;</span>
      </span>
    </vulnerability-detail>
  </div>
</template>
