# frozen_string_literal: true

module Gitlab
  module BackgroundMigration
    # This class populates the `finding_uuid` attribute for
    # the existing `vulnerability_feedback` records.
    class PopulateFindingUuidForVulnerabilityFeedback
      REPORT_TYPES = {
        sast: 0,
        dependency_scanning: 1,
        container_scanning: 2,
        dast: 3,
        secret_detection: 4,
        coverage_fuzzing: 5,
        api_fuzzing: 6
      }.freeze

      class VulnerabilityFeedback < ActiveRecord::Base # rubocop:disable Style/Documentation
        include EachBatch

        self.table_name = 'vulnerability_feedback'

        enum category: REPORT_TYPES

        scope :in_range, -> (start, stop) { where(id: start..stop) }
        scope :without_uuid, -> { where(finding_uuid: nil) }

        def self.load_vulnerability_findings
          all.to_a.tap { |collection| collection.each(&:vulnerability_finding) }
        end

        def set_finding_uuid
          return unless vulnerability_finding.present? && vulnerability_finding.primary_identifier.present?

          update_column(:finding_uuid, calculated_uuid)
        rescue StandardError => error
          Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error)
        end

        def vulnerability_finding
          BatchLoader.for(finding_key).batch(replace_methods: false) do |finding_keys, loader|
            project_ids = finding_keys.map { |key| key[:project_id] }
            categories = finding_keys.map { |key| key[:category] }
            fingerprints = finding_keys.map { |key| key[:project_fingerprint] }

            findings = Finding.with_primary_identifier.where(
              project_id: project_ids.uniq,
              report_type: categories.uniq,
              project_fingerprint: fingerprints.uniq
            ).to_a

            finding_keys.each do |finding_key|
              loader.call(
                finding_key,
                findings.find { |f| finding_key == f.finding_key }
              )
            end
          end
        end

        private

        def calculated_uuid
          ::Security::VulnerabilityUUID.generate(
            report_type: category,
            primary_identifier_fingerprint: vulnerability_finding.primary_identifier.fingerprint,
            location_fingerprint: vulnerability_finding.location_fingerprint,
            project_id: project_id
          )
        end

        def finding_key
          {
            project_id: project_id,
            category: category,
            project_fingerprint: project_fingerprint
          }
        end
      end

      class Finding < ActiveRecord::Base # rubocop:disable Style/Documentation
        include ShaAttribute

        self.table_name = 'vulnerability_occurrences'

        sha_attribute :project_fingerprint
        sha_attribute :location_fingerprint

        belongs_to :primary_identifier, class_name: 'Gitlab::BackgroundMigration::PopulateFindingUuidForVulnerabilityFeedback::Identifier'

        enum report_type: REPORT_TYPES

        scope :with_primary_identifier, -> { includes(:primary_identifier) }

        def finding_key
          {
            project_id: project_id,
            category: report_type,
            project_fingerprint: project_fingerprint
          }
        end
      end

      class Identifier < ActiveRecord::Base # rubocop:disable Style/Documentation
        self.table_name = 'vulnerability_identifiers'
      end

      def perform(*range)
        feedback = VulnerabilityFeedback.without_uuid.in_range(*range).load_vulnerability_findings
        feedback.each(&:set_finding_uuid)

        log_info(feedback.count)
      end

      def log_info(feedback_count)
        ::Gitlab::BackgroundMigration::Logger.info(
          migrator: self.class.name,
          message: '`finding_uuid` attributes has been set',
          count: feedback_count
        )
      end
    end
  end
end
