module Projects
  class PropagateServiceTemplate
    BATCH_SIZE = 100

    def self.propagate(*args)
      new(*args).propagate
    end

    def initialize(template)
      @template = template
    end

    def propagate
      return unless @template.active?

      Rails.logger.info("Propagating services for template #{@template.id}")

      propagate_projects_with_template
    end

    private

    def propagate_projects_with_template
      loop do
        batch = project_ids_batch

        bulk_create_from_template(batch) unless batch.empty?

        break if batch.size < BATCH_SIZE
      end
    end

    def bulk_create_from_template(batch)
      service_list = batch.map do |project_id|
        service_hash.values << project_id
      end

      Project.transaction do
        bulk_insert_services(service_hash.keys << 'project_id', service_list)
        run_callbacks(batch)
      end
    end

    def project_ids_batch
      Project.connection.select_values(
        <<-SQL
          SELECT id
          FROM projects
          WHERE NOT EXISTS (
            SELECT true
            FROM services
            WHERE services.project_id = projects.id
            AND services.type = '#{@template.type}'
          )
          AND projects.pending_delete = false
          AND projects.archived = false
          LIMIT #{BATCH_SIZE}
      SQL
      )
    end

    def bulk_insert_services(columns, values_array)
      ActiveRecord::Base.connection.execute(
        <<-SQL.strip_heredoc
          INSERT INTO services (#{columns.join(', ')})
          VALUES #{values_array.map { |tuple| "(#{tuple.join(', ')})" }.join(', ')}
      SQL
      )
    end

    def service_hash
      @service_hash ||=
        begin
          template_hash = @template.as_json(methods: :type).except('id', 'template', 'project_id')

          template_hash.each_with_object({}) do |(key, value), service_hash|
            value = value.is_a?(Hash) ? value.to_json : value

            service_hash[ActiveRecord::Base.connection.quote_column_name(key)] =
              ActiveRecord::Base.sanitize(value)
          end
        end
    end

    def run_callbacks(batch)
      if active_external_issue_tracker?
        Project.where(id: batch).update_all(has_external_issue_tracker: true)
      end

      if active_external_wiki?
        Project.where(id: batch).update_all(has_external_wiki: true)
      end
    end

    def active_external_issue_tracker?
      @template.issue_tracker? && !@template.default
    end

    def active_external_wiki?
      @template.type == 'ExternalWikiService'
    end
  end
end
