# frozen_string_literal: true

module CreatesCommit
  extend ActiveSupport::Concern
  include Gitlab::Utils::StrongMemoize

  # rubocop:disable Gitlab/ModuleWithInstanceVariables
  def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil, target_project: nil)
    target_project ||= @project

    if user_access(target_project).can_push_to_branch?(branch_name_or_ref)
      @project_to_commit_into = target_project
      @branch_name ||= @ref
    else
      @project_to_commit_into = current_user.fork_of(target_project)
      @branch_name ||= @project_to_commit_into.repository.next_branch('patch')
    end

    @start_branch ||= @ref || @branch_name

    start_project = @project_to_commit_into

    commit_params = @commit_params.merge(
      start_project: start_project,
      start_branch: @start_branch,
      branch_name: @branch_name
    )

    result = service.new(@project_to_commit_into, current_user, commit_params).execute

    if result[:status] == :success
      update_flash_notice(success_notice)

      success_path = final_success_path(success_path, target_project)

      respond_to do |format|
        format.html { redirect_to success_path }
        format.json { render json: { message: _("success"), filePath: success_path } }
      end
    else
      flash[:alert] = result[:message]
      failure_path = failure_path.call if failure_path.respond_to?(:call)

      respond_to do |format|
        format.html do
          if failure_view
            render failure_view
          else
            redirect_to failure_path
          end
        end
        format.json { render json: { message: _("failed"), filePath: failure_path } }
      end
    end
  end
  # rubocop:enable Gitlab/ModuleWithInstanceVariables

  def authorize_edit_tree!
    return if can_collaborate_with_project?(project, ref: branch_name_or_ref)

    access_denied!
  end

  private

  def update_flash_notice(success_notice)
    flash[:notice] = success_notice || _("Your changes have been successfully committed.")

    if create_merge_request?
      flash[:notice] =
        if merge_request_exists?
          nil
        else
          mr_message =
            if different_project?
              _("You can now submit a merge request to get this change into the original project.")
            else
              _("You can now submit a merge request to get this change into the original branch.")
            end

          flash[:notice] += " #{mr_message}"
        end
    end
  end

  def final_success_path(success_path, target_project)
    if create_merge_request?
      merge_request_exists? ? existing_merge_request_path : new_merge_request_path(target_project)
    else
      success_path = success_path.call if success_path.respond_to?(:call)

      success_path
    end
  end

  # rubocop:disable Gitlab/ModuleWithInstanceVariables
  def new_merge_request_path(target_project)
    project_new_merge_request_path(
      @project_to_commit_into,
      merge_request: {
        target_project_id: @project_to_commit_into.default_merge_request_target.id,
        source_branch: @branch_name,
        target_branch: @start_branch
      }
    )
  end
  # rubocop:enable Gitlab/ModuleWithInstanceVariables

  def existing_merge_request_path
    project_merge_request_path(@project, @merge_request) # rubocop:disable Gitlab/ModuleWithInstanceVariables
  end

  # rubocop:disable Gitlab/ModuleWithInstanceVariables
  # rubocop: disable CodeReuse/ActiveRecord
  def merge_request_exists?
    strong_memoize(:merge_request) do
      MergeRequestsFinder.new(current_user, project_id: @project.id)
        .execute
        .opened
        .find_by(
          source_project_id: @project_to_commit_into,
          source_branch: @branch_name,
          target_branch: @start_branch)
    end
  end
  # rubocop: enable CodeReuse/ActiveRecord
  # rubocop:enable Gitlab/ModuleWithInstanceVariables

  def different_project?
    @project_to_commit_into != @project # rubocop:disable Gitlab/ModuleWithInstanceVariables
  end

  def create_merge_request?
    # Even if the field is set, if we're checking the same branch
    # as the target branch in the same project,
    # we don't want to create a merge request.
    params[:create_merge_request].present? &&
      (different_project? || @start_branch != @branch_name) # rubocop:disable Gitlab/ModuleWithInstanceVariables
  end

  def branch_name_or_ref
    @branch_name || @ref # rubocop:disable Gitlab/ModuleWithInstanceVariables
  end
end