<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import { bodyWithFallBack } from 'ee/vue_shared/security_reports/components/helpers';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import { SUPPORTING_MESSAGE_TYPES } from 'ee/vulnerabilities/constants';
import { s__, __ } from '~/locale';
import CodeBlock from '~/vue_shared/components/code_block.vue';
import DetailItem from './detail_item.vue';
import VulnerabilityDetailSection from './vulnerability_detail_section.vue';

export default {
  name: 'VulnerabilityDetails',
  components: {
    CodeBlock,
    GlLink,
    SeverityBadge,
    DetailItem,
    GlSprintf,
    VulnerabilityDetailSection,
  },
  props: {
    vulnerability: {
      type: Object,
      required: true,
    },
  },
  computed: {
    location() {
      return this.vulnerability.location || {};
    },
    stacktraceSnippet() {
      return this.vulnerability.stacktraceSnippet || '';
    },
    scanner() {
      return this.vulnerability.scanner || {};
    },
    fileText() {
      return (this.location.file || '') + (this.lineNumber ? `:${this.lineNumber}` : '');
    },
    fileUrl() {
      return (this.location.blobPath || '') + (this.lineNumber ? `#L${this.lineNumber}` : '');
    },
    lineNumber() {
      const { startLine: start, endLine: end } = this.location;
      return end > start ? `${start}-${end}` : start;
    },
    scannerUrl() {
      return this.scanner.url || '';
    },
    scannerDetails() {
      if (this.scannerUrl) {
        return {
          component: 'GlLink',
          properties: {
            href: this.scannerUrl,
            target: '_blank',
          },
        };
      }

      return {
        component: 'span',
        properties: {},
      };
    },
    assertion() {
      return this.vulnerability.evidenceSource?.name;
    },
    recordedMessage() {
      return this.vulnerability?.supportingMessages?.find(
        (msg) => msg.name === SUPPORTING_MESSAGE_TYPES.RECORDED,
      )?.response;
    },
    constructedRequest() {
      return this.constructRequest(this.vulnerability.request);
    },
    constructedResponse() {
      return this.constructResponse(this.vulnerability.response);
    },
    constructedRecordedResponse() {
      return this.constructResponse(this.recordedMessage);
    },
    requestData() {
      if (!this.vulnerability.request) {
        return [];
      }

      return [
        {
          label: __('%{labelStart}Sent request:%{labelEnd} %{headers}'),
          content: this.constructedRequest,
          isCode: true,
        },
      ].filter((x) => x.content);
    },
    responseData() {
      if (!this.vulnerability.response) {
        return [];
      }

      return [
        {
          label: __('%{labelStart}Actual response:%{labelEnd} %{headers}'),
          content: this.constructedResponse,
          isCode: true,
        },
      ].filter((x) => x.content);
    },
    recordedResponseData() {
      if (!this.recordedMessage) {
        return [];
      }

      return [
        {
          label: __('%{labelStart}Unmodified response:%{labelEnd} %{headers}'),
          content: this.constructedRecordedResponse,
          isCode: true,
        },
      ].filter((x) => x.content);
    },
    shouldShowLocation() {
      return (
        this.location.crashAddress ||
        this.location.crashType ||
        this.location.stacktraceSnippet ||
        this.location.file ||
        this.location.image ||
        this.location.operatingSystem
      );
    },
    hasRequest() {
      return Boolean(this.requestData.length);
    },
    hasResponse() {
      return Boolean(this.responseData.length);
    },
    hasRecordedResponse() {
      return Boolean(this.recordedResponseData.length);
    },
    hasResponses() {
      return Boolean(this.hasResponse || this.hasRecordedResponse);
    },
  },
  methods: {
    getHeadersAsCodeBlockLines(headers) {
      return Array.isArray(headers)
        ? headers.map(({ name, value }) => `${name}: ${value}`).join('\n')
        : '';
    },
    constructResponse(response) {
      const { body, statusCode, reasonPhrase = '', headers = [] } = 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('')
        : '';
    },
  },
  i18n: {
    requestResponse: s__('Vulnerability|Request/Response'),
    unmodifiedResponse: s__(
      'Vulnerability|The unmodified response is the original response that had no mutations done to the request',
    ),
    actualResponse: s__(
      'Vulnerability|Actual received response is the one received when this fault was detected',
    ),
  },
};
</script>

<template>
  <div class="md" data-qa-selector="vulnerability_details">
    <h1
      class="mt-3 mb-2 border-bottom-0"
      data-testid="title"
      data-qa-selector="vulnerability_title"
    >
      {{ vulnerability.title }}
    </h1>
    <h3 class="mt-0">{{ __('Description') }}</h3>
    <p data-testid="description" data-qa-selector="vulnerability_description">
      {{ vulnerability.description }}
    </p>

    <ul>
      <detail-item :sprintf-message="__('%{labelStart}Severity:%{labelEnd} %{severity}')">
        <severity-badge :severity="vulnerability.severity" class="gl-display-inline ml-1" />
      </detail-item>
      <detail-item :sprintf-message="__('%{labelStart}Scan Type:%{labelEnd} %{reportType}')"
        >{{ vulnerability.reportType }}
      </detail-item>
      <detail-item
        v-if="scanner.name"
        :sprintf-message="__('%{labelStart}Scanner:%{labelEnd} %{scanner}')"
      >
        <component
          :is="scannerDetails.component"
          v-bind="scannerDetails.properties"
          data-testid="scannerSafeLink"
        >
          <gl-sprintf
            v-if="scanner.version"
            :message="s__('Vulnerability|%{scannerName} (version %{scannerVersion})')"
          >
            <template #scannerName>{{ scanner.name }}</template>
            <template #scannerVersion>{{ scanner.version }}</template>
          </gl-sprintf>
          <template v-else>{{ scanner.name }}</template>
        </component>
      </detail-item>
      <detail-item
        v-if="location.class"
        :sprintf-message="__('%{labelStart}Class:%{labelEnd} %{class}')"
        >{{ location.class }}
      </detail-item>
      <detail-item
        v-if="location.method"
        :sprintf-message="__('%{labelStart}Method:%{labelEnd} %{method}')"
      >
        <code>{{ location.method }}</code>
      </detail-item>
      <detail-item
        v-if="vulnerability.evidence"
        :sprintf-message="__('%{labelStart}Evidence:%{labelEnd} %{evidence}')"
        >{{ vulnerability.evidence }}
      </detail-item>
    </ul>

    <template v-if="shouldShowLocation">
      <h3>{{ __('Location') }}</h3>
      <ul>
        <detail-item
          v-if="location.image"
          :sprintf-message="__('%{labelStart}Image:%{labelEnd} %{image}')"
          >{{ location.image }}
        </detail-item>
        <detail-item
          v-if="location.operatingSystem"
          :sprintf-message="__('%{labelStart}Namespace:%{labelEnd} %{namespace}')"
          >{{ location.operatingSystem }}
        </detail-item>
        <detail-item
          v-if="location.file"
          :sprintf-message="__('%{labelStart}File:%{labelEnd} %{file}')"
        >
          <gl-link :href="fileUrl" target="_blank">{{ fileText }}</gl-link>
        </detail-item>
        <detail-item
          v-if="location.crashAddress"
          :sprintf-message="__('%{labelStart}Crash Address:%{labelEnd} %{crash_address}')"
          >{{ location.crashAddress }}
        </detail-item>
        <detail-item
          v-if="location.stacktraceSnippet"
          :sprintf-message="__('%{labelStart}Crash State:%{labelEnd} %{stacktrace_snippet}')"
        >
          <code-block :code="location.stacktraceSnippet" max-height="225px" />
        </detail-item>
      </ul>
    </template>

    <template v-if="vulnerability.links && vulnerability.links.length">
      <h3>{{ __('Links') }}</h3>
      <ul>
        <li
          v-for="(link, index) in vulnerability.links"
          :key="`${index}:${link.url}`"
          class="gl-ml-0! gl-list-style-position-inside"
        >
          <gl-link
            :href="link.url"
            data-testid="link"
            target="_blank"
            :aria-label="__('Third Party Advisory Link')"
            :title="link.url"
          >
            {{ link.url }}
          </gl-link>
        </li>
      </ul>
    </template>

    <template v-if="vulnerability.identifiers && vulnerability.identifiers.length">
      <h3>{{ __('Identifiers') }}</h3>
      <ul>
        <li
          v-for="(identifier, index) in vulnerability.identifiers"
          :key="`${index}:${identifier.url}`"
          class="gl-ml-0! gl-list-style-position-inside"
        >
          <component
            :is="identifier.url ? 'gl-link' : 'span'"
            v-bind="identifier.url && { href: identifier.url, target: '_blank' }"
            data-testid="identifier"
          >
            {{ identifier.name }}
          </component>
        </li>
      </ul>
    </template>
    <vulnerability-detail-section
      v-if="hasRequest"
      data-testid="request"
      :list-data="requestData"
      :heading="$options.i18n.requestResponse"
    />

    <div v-if="hasResponses" class="row">
      <vulnerability-detail-section
        v-if="hasRecordedResponse"
        data-testid="recorded-response"
        :class="hasResponse ? 'col-6' : 'col'"
        :list-data="recordedResponseData"
        :icon-title="$options.i18n.unmodifiedResponse"
      />

      <vulnerability-detail-section
        v-if="hasResponse"
        data-testid="response"
        :class="hasRecordedResponse ? 'col-6' : 'col'"
        :list-data="responseData"
        :icon-title="$options.i18n.actualResponse"
      />
    </div>

    <template v-if="assertion">
      <h3>{{ s__('Vulnerability|Additional Info') }}</h3>
      <ul>
        <detail-item :sprintf-message="__('%{labelStart}Assert:%{labelEnd} %{assertion}')">
          {{ assertion }}
        </detail-item>
      </ul>
    </template>

    <template v-if="vulnerability.assets && vulnerability.assets.length">
      <h3>{{ s__('Vulnerability|Reproduction Assets') }}</h3>
      <ul>
        <li
          v-for="(asset, index) in vulnerability.assets"
          :key="`${index}:${asset.url}`"
          class="gl-ml-0! gl-list-style-position-inside"
        >
          <component
            :is="asset.url ? 'gl-link' : 'span'"
            v-bind="asset.url && { href: asset.url, target: '_blank' }"
            data-testid="asset"
          >
            {{ asset.name }}
          </component>
        </li>
      </ul>
    </template>
  </div>
</template>
