# frozen_string_literal: true

module GDK
  module Diagnostic
    class RubyGems < Base
      TITLE = 'Ruby Gems'
      GEM_REQUIRE_MAPPING = {
        'charlock_holmes' => 'charlock_holmes',
        'ffi' => 'ffi',
        'gpgme' => 'gpgme',
        'pg' => 'pg',
        'oj' => 'oj'
      }.freeze
      GITLAB_GEMS_WITH_C_CODE_TO_CHECK = GEM_REQUIRE_MAPPING.keys

      def initialize(allow_gem_not_installed: false)
        @allow_gem_not_installed = allow_gem_not_installed

        super()
      end

      def success?
        return false unless bundle_check_ok?

        failed_to_load_gitlab_gems.empty?
      end

      def detail
        return if success?

        return bundle_check_error_message unless bundle_check_ok?

        gitlab_error_message
      end

      private

      def bundle_check_ok?
        exec_cmd("#{bundle_exec_cmd} bundle check") || allow_gem_not_installed?
      end

      def allow_gem_not_installed?
        @allow_gem_not_installed == true
      end

      def failed_to_load_gitlab_gems
        @failed_to_load_gitlab_gems ||= failed_to_load_gitlab_gems_parallel(GITLAB_GEMS_WITH_C_CODE_TO_CHECK)
      end

      def failed_to_load_gitlab_gems_parallel(names)
        names
          .map { |name| Thread.new { Thread.current[:name] = name unless gem_ok?(name) } }
          .filter_map { |thread| thread.join[:name] }
      end

      def gem_ok?(name)
        # We need to support the situation where it's OK if a Ruby gem is not
        # installed because we could be about to install the GDK for the very
        # first time and the Ruby gem won't be installed.
        gem_installed?(name) ? gem_loads_ok?(name) : allow_gem_not_installed?
      end

      def bundle_exec_cmd
        @bundle_exec_cmd ||= config.gdk_root.join('support', 'bundle-exec')
      end

      def gem_installed?(name)
        exec_cmd("#{bundle_exec_cmd} gem list -i #{name}")
      end

      def gem_loads_ok?(name)
        gem_name = GEM_REQUIRE_MAPPING[name]
        command = -> { exec_cmd("#{bundle_exec_cmd} ruby -r #{gem_name} -e 'nil'") }

        if GDK::Dependencies.bundler_loaded?
          ::Bundler.with_unbundled_env do
            command.call
          end
        else
          command.call
        end
      end

      def exec_cmd(cmd)
        GDK::Output.debug("cmd=[#{cmd}]")

        Shellout.new(cmd, chdir: config.gitlab.dir.to_s).execute(display_output: false, display_error: false).success?
      end

      def bundle_check_error_message
        <<~MESSAGE
          There are Ruby gems missing that need to be installed. Try running the following to fix:

            (cd #{config.gitlab.dir} && bundle install)
        MESSAGE
      end

      def gitlab_error_message
        <<~MESSAGE
          The following Ruby Gems appear to have issues:

          #{@failed_to_load_gitlab_gems.join("\n")}

          Try running the following to fix:

            (cd #{config.gitlab.dir} && bundle pristine #{@failed_to_load_gitlab_gems.join(' ')})
        MESSAGE
      end
    end
  end
end
