# frozen_string_literal: true

module ReleaseTools
  module Security
    # Merging valid security merge requests
    class MergeRequestsMerger
      include ::SemanticLogger::Loggable

      # @param [ReleaseTools::Security::Client] client
      def initialize(merge_default: false)
        @client = ReleaseTools::Security::Client.new
        @merge_default = merge_default
        @status = nil
      end

      # Processes valid security merge requests:
      #
      # 1. Fetches security implementation issues that are ready
      #    to be processed.
      # 2. Iterates over security implementation issues
      # 3. If `merge_default`, only the merge request targeting the default branch is processed.
      #    Else, the backports and pending merge requests are processed.
      def execute
        security_implementation_issues.each do |security_issue|
          process_merge_requests(security_issue)
        end

        post_security_issues_table

        update_patch_release_status_metric

        update_status(:success)
        send_slack_notification
      rescue StandardError => ex
        logger.fatal('There has been an error. Check the logs for which MRs were not processed correctly.', error: ex)

        update_status(:failed)
        send_slack_notification

        raise
      end

      private

      def security_implementation_issues
        Security::IssueCrawler
          .new
          .upcoming_security_issues_and_merge_requests
      end

      def process_merge_requests(security_issue)
        if @merge_default
          process_mr_targeting_default(security_issue)
        else
          process_pending_merge_requests(security_issue)
        end
      end

      # Processes merge request targeting the default branch for GitLab Security
      # issues by triggering a new pipeline and setting MWPS
      def process_mr_targeting_default(security_issue)
        return unless security_issue.allowed_to_early_merge?

        logger.info('Processing merge request targeting default branch', issue: security_issue.web_url)

        mr_default = security_issue.merge_request_targeting_default_branch

        ReleaseTools::Security::MergeWhenPipelineSucceedsService
          .new(@client, mr_default)
          .execute
      end

      # Processes pending merge requests:
      #
      # - For GitLab Security issues, the backports are processed.
      # - For the rest of the projects, all security merge requests
      #   are processed.
      def process_pending_merge_requests(security_issue)
        unless security_issue.default_merge_request_handled?
          logger.fatal('Issue skipped. Default merge request has not been merged', issue: security_issue.web_url)
          update_status(:failed)

          return
        end

        logger.info('Merging pending merge_requests', issue: security_issue.web_url)

        security_issue.pending_merge_requests.each do |merge_request|
          merge_merge_request(merge_request)
        end
      end

      def merge_merge_request(merge_request)
        logger.trace(__method__, merge_request: merge_request.web_url)

        return if SharedStatus.dry_run? || merge_request.state == 'merged'

        merged_result = @client.accept_merge_request(
          merge_request.project_id,
          merge_request.iid,
          squash: true,
          should_remove_source_branch: true
        )

        if merged_result.respond_to?(:merge_commit_sha) && merged_result.merge_commit_sha.present?
          logger.info("Merged security merge request", url: merge_request.web_url)
        else
          logger.fatal("Merge request #{merge_request.web_url} couldn't be merged")
          update_status(:failed)
        end
      end

      def post_security_issues_table
        IssueTable::Service.new(@client).execute
      end

      def update_patch_release_status_metric
        return if SharedStatus.dry_run?

        ReleaseTools::Metrics::PatchReleaseStatus.new(status: :closed).execute
      end

      def update_status(value)
        return if @status == :failed

        @status = value
      end

      def send_slack_notification
        job_type = if @merge_default
                     "Default merge"
                   else
                     "Merge"
                   end

        ReleaseTools::Slack::ReleaseJobEndNotifier.new(
          job_type: job_type,
          status: @status,
          release_type: :patch
        ).send_notification
      end
    end
  end
end
