# frozen_string_literal: true

module ReleaseTools
  module Promotion
    # Perform production checks and report on their statuses
    #
    # There is a default group of checks that are always queried. Additional
    # checks can be included by passing them to the initializer:
    #
    #   # Additionally include `MyCustomCheck` and `ConditionalCheck`
    #   ProductionStatus.new(:my_custom_check, :conditional_check)
    class ProductionStatus
      include ::SemanticLogger::Loggable
      include Check

      # Raised when attempting to include an unknown `Check` class
      class InvalidCheckError < StandardError
        def initialize(name)
          super("Unknown check `#{name}`")
        end
      end

      # List of checks that are always included for any production status check
      ALL_CHECKS = %i[
        active_incidents
        change_requests
        gitlab_deployment_health
        orchestrator_status
      ].freeze

      attr_reader :checks, :scope

      def initialize(*extra_checks, scope: :deployment)
        @scope = scope

        @checks = [*ALL_CHECKS, *extra_checks].uniq.map do |check_name|
          instance_of_check(check_name)
        end
      end

      # @see Check#fine?
      def fine?
        checks.all?(&:fine?)
      end

      # @return <Array(Class)> Array of checks that failed.
      def failed_checks
        checks.reject(&:fine?)
      end

      # @see Check#to_issue_body
      def to_issue_body
        text = StringIO.new

        overall_status = if fine?
                           "#{ok_icon} Production checks pass."
                         else
                           "#{failure_icon} Production checks fail!"
                         end

        text.puts(overall_status)

        checks.each do |check|
          text.puts("\n---")
          text.puts(check.to_issue_body)
        end

        text.string
      end

      # @see Check#to_slack_blocks
      def to_slack_blocks
        overall_status = if fine?
                           "#{ok_icon} Production checks pass. :shipit: :fine:"
                         else
                           "#{failure_icon} Production checks fail!"
                         end

        if fine?
          [to_slack_block(overall_status)]
        else
          [to_slack_block(overall_status)] + checks.reject(&:fine?).map(&:to_slack_blocks).flatten
        end
      end

      private

      def to_slack_block(status)
        {
          type: 'section',
          text: mrkdwn(status)
        }
      end

      def args_for(check_name)
        if %i[active_incidents change_requests].include?(check_name)
          { scope: scope }
        else
          {}
        end
      end

      def instance_of_check(check_name)
        Checks.const_get(check_name.to_s.camelize).new(**args_for(check_name))
      rescue NameError => ex
        raise InvalidCheckError, ex.name
      end
    end
  end
end
