# frozen_string_literal: true

module ReleaseTools
  module Services
    class BasePublishService
      include ::SemanticLogger::Loggable

      class PipelineNotFoundError < StandardError
        def initialize(version)
          super("Pipeline not found for #{version}")
        end
      end

      def initialize(version)
        @version = version
      end

      def play_stages
        raise NotImplementedError
      end

      def release_versions
        raise NotImplementedError
      end

      def project
        raise NotImplementedError
      end

      def execute
        logger.info('Publishing versions', release_versions: release_versions)

        all_played_pipelines = []
        all_failed_pipelines = []

        release_versions.each do |version|
          pipeline = pipeline_for_version(version)

          if pipeline.status == 'failed'
            all_failed_pipelines.append(pipeline)
            logger.warn("Pipeline failed. Not searching for manual jobs.", pipeline: pipeline.web_url, version: version)
            next
          end

          played_pipeline = publish_version(version, pipeline)
          all_played_pipelines.append(played_pipeline) if played_pipeline
        end

        [all_played_pipelines, all_failed_pipelines]
      end

      private

      def publish_version(version, pipeline)
        logger.info('Finding manual jobs', pipeline: pipeline.web_url, version: version)
        triggers = jobs_to_be_triggered(pipeline)
        logger.info('Found jobs to be triggered', triggers_count: triggers.count)

        if triggers.any?
          play_manual_jobs(triggers)

          return pipeline
        end

        # If there are no manual jobs, and the pipeline has not failed,
        # maybe it is just a pipeline that has no manual jobs
        logger.warn('No jobs found', url: pipeline.web_url)
        nil
      end

      def pipeline_for_version(version)
        pipeline =
          Retriable.with_context(:api) do
            client
              .pipelines(project, scope: :tags, ref: version)
              .first
          end

        raise PipelineNotFoundError.new(version) unless pipeline

        pipeline
      end

      def jobs_to_be_triggered(pipeline)
        Retriable.with_context(:api) do
          client
            .pipeline_jobs(project, pipeline.id, scope: :manual, per_page: 100)
            .auto_paginate
            .select { |job| play_stages.include?(job.stage) }
        end
      end

      def play_manual_jobs(triggers)
        triggers.each do |job|
          if SharedStatus.dry_run?
            logger.info('Would play job', name: job.name, url: job.web_url)
            next
          end

          logger.info('Play job', name: job.name, url: job.web_url)
          Retriable.with_context(:api) do
            client.job_play(project_path, job.id)
          end
        end
      end

      def project_path
        project.dev_path
      end

      def client
        @client ||= GitlabDevClient
      end
    end
  end
end
