# frozen_string_literal: true

module GDK
  module Command
    class Doctor < BaseCommand
      def initialize(diagnostics: GDK::Diagnostic.all, **args)
        @diagnostics = diagnostics
        @unexpected_error = false
        @should_autocorrect = false

        super(**args)
      end

      def run(args = [])
        unless installed?
          out.warn("GDK has not been installed so cannot run 'gdk doctor'. Try running `gem install gitlab-development-kit` again.")
          return false
        end

        @should_autocorrect = args.intersect?(['--correct', '-correct', '-C'])

        return false unless start_necessary_services

        show_results(diagnostic_results)
        return 2 if @unexpected_error

        handle_correctable_results(correctable_results)
        return 2 if @unexpected_error

        diagnostic_results.empty?
      end

      private

      attr_reader :diagnostics

      def installed?
        # TODO: Eventually, the Procfile will no longer exists so we need a better
        # way to determine this, but this will be OK for now.
        GDK.root.join('Procfile').exist?
      end

      def diagnostic_results
        @diagnostic_results ||= jobs.filter_map { |x| x.join[:results] }
      end

      def correctable_results
        @correctable_results ||= diagnostic_results.reject(&:unexpected_error).select(&:correctable?)
      end

      def jobs
        diagnostics.map do |diagnostic|
          Thread.new do
            Thread.current[:results] = perform_diagnosis_for(diagnostic)
            out.print(output_dot, stderr: true)
          end
        end
      end

      def perform_diagnosis_for(diagnostic)
        diagnostic unless diagnostic.success?
      rescue StandardError => e
        @unexpected_error = true
        diagnostic.unexpected_error = e
        diagnostic
      end

      def perform_corrections
        correctable_results.map do |diagnostic|
          perform_correction_for(diagnostic)
        end
      end

      def perform_correction_for(diagnostic)
        out.print("Performing correction for '#{diagnostic.title}' ")
        diagnostic.correct!
        out.success(out.wrap_in_color('success', Output::COLOR_CODE_GREEN))
      rescue StandardError => e
        @unexpected_error = true
        out.error(e.message, e)
      end

      def handle_correctable_results(correctable_results)
        out.puts("\n")

        if correctable_results.any?
          if @should_autocorrect
            out.divider
            perform_corrections
          else
            out.info("You may autocorrect #{correctable_results.size} #{correctable_results.size == 1 ? 'problem' : 'problems'} by running `gdk doctor --correct` or `gdk doctor -C`")
          end
        elsif @should_autocorrect
          out.warn('No problems to autocorrect.')
        end
      end

      def start_necessary_services
        postgresql = Postgresql.new
        return true if postgresql.ready?(try_times: 1, quiet: true)

        Runit.start('postgresql', quiet: true)

        postgresql.ready?(try_times: 20, interval: 0.5)
      end

      def show_results(results)
        out.puts("\n")
        return out.success('Your GDK is healthy.') unless results.any?

        out.warn('Your GDK may need attention.')
        results.each { |diagnostic| out.puts(diagnostic.message) }
      end

      def output_dot
        return out.wrap_in_color('E', Output::COLOR_CODE_RED) if Thread.current[:results]&.unexpected_error
        return out.wrap_in_color('W', Output::COLOR_CODE_YELLOW) if Thread.current[:results]

        out.wrap_in_color('.', Output::COLOR_CODE_GREEN)
      end
    end
  end
end
