# frozen_string_literal: true

module ReleaseTools
  class PassingBuild
    include ::SemanticLogger::Loggable

    attr_reader :ref

    def initialize(ref, project = ReleaseTools::Project::GitlabEe, client: ReleaseTools::GitlabClient)
      @project = project
      @ref = ref
      @client = client
    end

    def for_auto_deploy_branch
      @for_auto_deploy_branch ||= passing_build_commit(:branch)
    end

    def for_auto_deploy_tag
      @for_auto_deploy_tag ||= passing_build_commit(:tag)
    end

    def success_for_auto_deploy_rollout?(sha)
      success?(sha, :rollout)
    end

    # Find the latest successful commit for the current project and ref.
    # @return [ObjectifiedHash, nil] Return the newest commit with a successful
    #         pipeline
    def latest_successful
      commits.detect { |commit| success?(commit.id, :latest_successful) }
    end

    def next_commit
      commits.next_commit(for_auto_deploy_branch.id)
    end

    def previous_package_sha
      commits.find_last_auto_deploy_limit_sha
    end

    private

    def commits
      @commits ||= ReleaseTools::Commits.new(@project, ref: ref, client: @client)
    end

    def passing_build_commit(operation)
      return commits.latest if Feature.enabled?(:auto_deploy_tag_latest)

      commit = commits.latest_successful_on_build(since_last_auto_deploy: true) do |sha|
        next true if success?(sha, operation)

        logger.info(
          'Skipping commit because the pipeline did not succeed',
          commit: sha
        )

        false
      end

      if commit.nil?
        raise "Unable to find a passing #{@project} build for `#{ref}` on dev"
      end

      commit
    end

    def success?(sha, operation)
      result = Retriable.with_context(:api) { @client.commit(@project.auto_deploy_path, ref: sha) }

      return success_for_gitlab(result, operation) if @project == ReleaseTools::Project::GitlabEe

      result.status == 'success'
    end

    def success_for_gitlab(commit, operation)
      logger.debug('validating gitlab commit', sha: commit.id, operation: operation, status: commit.status, url: commit.web_url)

      if commit.status == 'success'
        # Documentation-only changes result in a pipeline with only a few jobs.
        # If we were to include a passing documentation pipeline/commit, we may
        # end up also including code that broke a previous full pipeline.
        #
        # We also take into account QA only pipelines. These pipelines _should_ be
        # considered, but don't have a regular full pipeline. The "build-assets-image"
        # job is present for both regular and QA pipelines, but not for
        # documentation pipelines; hence we check for the presence of this job.
        # This is more reliable than just checking the amount of jobs.
        full_pipeline?(commit.last_pipeline)
      elsif operation == :tag && commit.status != 'failed'
        # When tagging we already ensured a full pipeline with the branch creation,
        # in this case we are either looking at a second run of the same pipeline
        # from the auto_deploy branch or at a pick into auto-deploy.
        #
        # In both cases we are ok in allowing the tag operation if the pipeline
        # is not failed. The formal validation of a successful run will be
        # delayed before the rollout
        true
      else
        # When a new auto-deploy branch is created, some commits may only have a
        # pipeline on the default branch. It's also possible that there _is_ a
        # pipeline, but that it only recently started.
        #
        # In both cases it's fine to tag the commit if it passed on the default
        # branch.
        #
        # When we delay package validation it is possible that a SHA that was
        # tagged while running on the auto-deploy branch and green on the
        # default branch will be red on the auto-deploy branch.
        # This can happened because of test flakiness. To keep things aligned
        # with the upfront validation logic in case of failure on the GitLab
        # repository we consider the status on the default branch.
        success_on_default_branch?(commit.id)
      end
    end

    def full_pipeline?(pipeline)
      @client
        .pipeline_jobs(@project.auto_deploy_path, pipeline.id)
        .auto_paginate do |job|
          # Latest master pipelines will not contain  'build-assets-image' job anymore
          # To keep backward compatibility for older pipelines, retain the check for 'build-assets-image' job
          return true if %w[build-assets-image compile-production-assets].any? { |name| job.name.start_with?(name) }
        end

      false
    end

    def success_on_default_branch?(sha)
      @client
        .pipelines(
          @project.auto_deploy_path,
          sha: sha,
          ref: @project.default_branch,
          status: 'success'
        )
        .auto_paginate do |pipeline|
          return true if full_pipeline?(pipeline)
        end

      false
    end
  end
end
