# frozen_string_literal: true

module Gitlab
  module Ci
    module Reports
      module Security
        class Finding
          include ::VulnerabilityFindingHelpers

          attr_reader :compare_key
          attr_reader :confidence
          attr_reader :identifiers
          attr_reader :flags
          attr_reader :links
          attr_reader :location
          attr_reader :evidence
          attr_reader :metadata_version
          attr_reader :name
          attr_reader :old_location
          attr_reader :project_fingerprint
          attr_reader :report_type
          attr_reader :scanner
          attr_reader :scan
          attr_reader :severity
          attr_accessor :uuid
          attr_accessor :overridden_uuid
          attr_reader :remediations
          attr_reader :details
          attr_reader :signatures
          attr_reader :project_id
          attr_reader :original_data

          delegate :file_path, :start_line, :end_line, to: :location

          alias_method :cve, :compare_key

          def initialize(compare_key:, identifiers:, flags: [], links: [], remediations: [], location:, evidence:, metadata_version:, name:, original_data:, report_type:, scanner:, scan:, uuid:, confidence: nil, severity: nil, details: {}, signatures: [], project_id: nil, vulnerability_finding_signatures_enabled: false) # rubocop:disable Metrics/ParameterLists
            @compare_key = compare_key
            @confidence = confidence
            @identifiers = identifiers
            @flags = flags
            @links = links
            @location = location
            @evidence = evidence
            @metadata_version = metadata_version
            @name = name
            @original_data = original_data
            @report_type = report_type
            @scanner = scanner
            @scan = scan
            @severity = severity
            @uuid = uuid
            @remediations = remediations
            @details = details
            @signatures = signatures
            @project_id = project_id
            @vulnerability_finding_signatures_enabled = vulnerability_finding_signatures_enabled

            @project_fingerprint = generate_project_fingerprint
          end

          def to_hash
            %i[
              compare_key
              confidence
              identifiers
              flags
              links
              location
              evidence
              metadata_version
              name
              project_fingerprint
              raw_metadata
              report_type
              scanner
              scan
              severity
              uuid
              details
              signatures
              description
              message
              cve
              solution
            ].index_with do |key|
              public_send(key) # rubocop:disable GitlabSecurity/PublicSend
            end
          end

          def primary_identifier
            identifiers.first
          end

          def update_location(new_location)
            @old_location = location
            @location = new_location
          end

          def unsafe?(severity_levels, report_types)
            severity.to_s.in?(severity_levels) && (report_types.blank? || report_type.to_s.in?(report_types))
          end

          def eql?(other)
            return false unless report_type == other.report_type && primary_identifier_fingerprint == other.primary_identifier_fingerprint

            if @vulnerability_finding_signatures_enabled
              matches_signatures(other.signatures, other.uuid)
            else
              location.fingerprint == other.location.fingerprint
            end
          end

          def hash
            if @vulnerability_finding_signatures_enabled && !signatures.empty?
              highest_signature = signatures.max_by(&:priority)
              report_type.hash ^ highest_signature.signature_hex.hash ^ primary_identifier_fingerprint.hash
            else
              report_type.hash ^ location.fingerprint.hash ^ primary_identifier_fingerprint.hash
            end
          end

          def valid?
            scanner.present? && primary_identifier.present? && location.present? && uuid.present?
          end

          def keys
            @keys ||= identifiers.reject(&:type_identifier?).flat_map do |identifier|
              location_fingerprints.map do |location_fingerprint|
                FindingKey.new(location_fingerprint: location_fingerprint, identifier_fingerprint: identifier.fingerprint)
              end
            end.push(uuid)
          end

          def primary_identifier_fingerprint
            primary_identifier&.fingerprint
          end

          def <=>(other)
            if severity == other.severity
              compare_key <=> other.compare_key
            else
              ::Enums::Vulnerability.severity_levels[other.severity] <=>
                ::Enums::Vulnerability.severity_levels[severity]
            end
          end

          def scanner_order_to(other)
            return 1 unless scanner
            return -1 unless other&.scanner

            scanner <=> other.scanner
          end

          def has_signatures?
            signatures.present?
          end

          def false_positive?
            flags.any?(&:false_positive?)
          end

          def remediation_byte_offsets
            remediations.map(&:byte_offsets).compact
          end

          def raw_metadata
            @raw_metadata ||= original_data.to_json
          end

          def description
            original_data['description']
          end

          def message
            original_data['message']
          end

          def solution
            original_data['solution']
          end

          def location_data
            original_data['location']
          end

          def assets
            original_data['assets'] || []
          end

          # Returns either the max priority signature hex
          # or the location fingerprint
          def location_fingerprint
            location_fingerprints.first
          end

          private

          def generate_project_fingerprint
            Digest::SHA1.hexdigest(compare_key)
          end

          def location_fingerprints
            @location_fingerprints ||= signature_hexes << location&.fingerprint
          end

          # Returns the signature hexes in reverse priority order
          def signature_hexes
            return [] unless @vulnerability_finding_signatures_enabled && signatures.present?

            signatures.sort_by(&:priority).map(&:signature_hex).reverse
          end
        end
      end
    end
  end
end