# frozen_string_literal: true

module VulnerabilityFeedback
  class CreateService < ::BaseService
    def execute
      vulnerability_feedback = @project.vulnerability_feedback.find_or_init_for(create_params)

      raise Gitlab::Access::AccessDeniedError unless can?(current_user, :create_vulnerability_feedback, vulnerability_feedback)

      if vulnerability_feedback.for_issue? &&
          !vulnerability_feedback.vulnerability_data.blank?

        create_issue_for(vulnerability_feedback)
      elsif vulnerability_feedback.for_merge_request? &&
          !vulnerability_feedback.vulnerability_data.blank?

        create_merge_request_for(vulnerability_feedback)
      else
        vulnerability_feedback.save
      end

      if vulnerability_feedback.persisted?
        success(vulnerability_feedback)
      else
        rollback_merge_request(vulnerability_feedback.merge_request) if vulnerability_feedback.merge_request

        error(vulnerability_feedback.errors)
      end

    rescue ArgumentError => e
      # VulnerabilityFeedback relies on #enum attributes which raise this exception
      error(e.message)
    end

    private

    def create_params
      @params[:author] = @current_user
      @params.merge(comment_params)
    end

    def comment_params
      return {} unless @params[:comment].present?

      {
        comment_author: @current_user,
        comment_timestamp: Time.zone.now
      }
    end

    def success(vulnerability_feedback)
      super().merge(vulnerability_feedback: vulnerability_feedback)
    end

    def create_issue_for(vulnerability_feedback)
      # Wrap Feedback and Issue creation in the same transaction
      ActiveRecord::Base.transaction do
        result = Issues::CreateFromVulnerabilityDataService
          .new(@project, @current_user, vulnerability_feedback.vulnerability_data)
          .execute

        if result[:status] == :error
          vulnerability_feedback.errors[:issue] << result[:message]
          raise ActiveRecord::Rollback
        end

        issue = result[:issue]
        vulnerability_feedback.issue = issue

        # Ensure created association is rolled back if feedback can't be saved
        raise ActiveRecord::Rollback unless vulnerability_feedback.save
      end
    end

    def create_merge_request_for(vulnerability_feedback)
      result = MergeRequests::CreateFromVulnerabilityDataService
        .new(@project, @current_user, vulnerability_feedback.vulnerability_data)
        .execute

      if result[:status] == :success
        merge_request = result[:merge_request]
        vulnerability_feedback.merge_request = merge_request

        vulnerability_feedback.save
      else
        vulnerability_feedback.errors[:merge_request] << result[:message]
      end
    end

    # Gitaly RPCs cannot occur within a transaction so we must manually
    # rollback MR and branch creation
    def rollback_merge_request(merge_request)
      branch_name = merge_request.source_branch

      merge_request&.destroy &&
        DeleteBranchService.new(project, current_user).execute(branch_name)
    end
  end
end
