# frozen_string_literal: true

module ReleaseTools
  module Security
    class CvesIssue
      include ::SemanticLogger::Loggable

      YamlError = Class.new(StandardError)
      YamlMissingError = Class.new(YamlError)
      YamlInvalidError = Class.new(YamlError)
      YamlParseError = Class.new(YamlError)

      delegate :title, to: :issue

      # @param [Gitlab::ObjectifiedHash] issue is an object from the response of the issues list API
      def initialize(issue)
        @issue = issue
      end

      def impact
        yaml.dig('vulnerability', 'impact')
      end

      def vulnerability_description
        yaml.dig('vulnerability', 'description')
      end

      def credit
        yaml.dig('vulnerability', 'credit')
      end

      def cvss_string
        "CVSS:3.1/#{impact}"
      end

      def cvss_severity
        cvss.severity
      end

      def cvss_base_score
        cvss.base_score
      end

      def web_url
        issue.web_url
      end

      def affected_versions
        yaml.dig('vulnerability', 'product', 'affected_versions')
      end

      def pending_affected_versions?
        affected_versions.nil? || affected_versions.include?('TODO')
      end

      def fixed_versions
        yaml.dig('vulnerability', 'product', 'fixed_versions')
      end

      def pending_fixed_versions?
        fixed_versions.nil? || fixed_versions.include?('TODO')
      end

      def yaml
        return @yaml if defined?(@yaml)

        unless yaml_present?
          logger.error('CVE issue does not have YAML', issue: issue.web_url)
          raise YamlMissingError, 'CVE issue does not have YAML'
        end

        @yaml = load_yaml

        unless @yaml.is_a?(Hash)
          logger.error('CVE issue YAML is not a Hash', issue: issue.web_url, yaml_str: yaml_str)
          raise YamlInvalidError, 'CVE issue YAML is not a Hash'
        end

        @yaml
      end

      def yaml_str
        issue.description.gsub(/^.*```yaml\n|\n```.*$/m, '')
      end

      def yaml_present?
        yaml_str.present?
      end

      def valid_yaml?
        load_yaml
      rescue YamlParseError
        false
      end

      def cve_id
        return unless cve_label

        cve_label.gsub('::', '-').upcase
      end

      private

      attr_reader :issue

      def cvss
        CvssSuite.new(cvss_string)
      end

      def load_yaml
        YAML.safe_load(yaml_str)
      rescue Psych::DisallowedClass, Psych::BadAlias, Psych::SyntaxError => ex
        logger.error('CVE issue contains invalid YAML', issue: issue.web_url, yaml_str: yaml_str, error: ex.inspect)

        raise YamlParseError, 'CVE issue contains invalid YAML'
      end

      def cve_label
        issue.labels.find { |label| label.start_with?('cve::') }
      end
    end
  end
end
