# frozen_string_literal: true
require_relative "helper"
require "rubygems/command_manager"

class TestGemCommandManager < Gem::TestCase
  PROJECT_DIR = File.expand_path("../..", __dir__).tap(&Gem::UNTAINT)

  def setup
    super

    @command_manager = Gem::CommandManager.new
  end

  def test_find_command
    command = @command_manager.find_command "install"

    assert_kind_of Gem::Commands::InstallCommand, command

    command = @command_manager.find_command "ins"

    assert_kind_of Gem::Commands::InstallCommand, command
  end

  def test_find_command_ambiguous
    e = assert_raise Gem::CommandLineError do
      @command_manager.find_command "u"
    end

    assert_equal "Ambiguous command u matches [uninstall, unpack, update]",
                 e.message
  end

  def test_find_alias_command
    command = @command_manager.find_command "i"

    assert_kind_of Gem::Commands::InstallCommand, command
  end

  def test_find_login_alias_command
    command = @command_manager.find_command "login"

    assert_kind_of Gem::Commands::SigninCommand, command
  end

  def test_find_logout_alias_comamnd
    command = @command_manager.find_command "logout"

    assert_kind_of Gem::Commands::SignoutCommand, command
  end

  def test_find_command_ambiguous_exact
    ins_command = Class.new
    Gem::Commands.send :const_set, :InsCommand, ins_command

    @command_manager.register_command :ins

    command = @command_manager.find_command "ins"

    assert_kind_of ins_command, command
  ensure
    Gem::Commands.send :remove_const, :InsCommand
  end

  def test_find_command_unknown
    e = assert_raise Gem::UnknownCommandError do
      @command_manager.find_command "xyz"
    end

    assert_equal "Unknown command xyz", e.message
  end

  def test_find_command_unknown_suggestions
    e = assert_raise Gem::UnknownCommandError do
      @command_manager.find_command "pish"
    end

    message = "Unknown command pish".dup

    if defined?(DidYouMean::SPELL_CHECKERS) && defined?(DidYouMean::Correctable)
      message << "\nDid you mean?  \"push\""
    end

    if e.respond_to?(:detailed_message)
      actual_message = e.detailed_message(highlight: false).sub(/\A(.*?)(?: \(.+?\))/) { $1 }
    else
      actual_message = e.message
    end

    assert_equal message, actual_message
  end

  def test_run_interrupt
    old_load_path = $:.dup
    $: << File.expand_path("test/rubygems", PROJECT_DIR)
    Gem.load_env_plugins

    @command_manager.register_command :interrupt

    use_ui @ui do
      assert_raise Gem::MockGemUi::TermError do
        @command_manager.run %w[interrupt]
      end
      assert_equal "", ui.output
      assert_equal "ERROR:  Interrupted\n", ui.error
    end
  ensure
    $:.replace old_load_path
    Gem::CommandManager.reset
  end

  def test_run_crash_command
    old_load_path = $:.dup
    $: << File.expand_path("test/rubygems", PROJECT_DIR)

    @command_manager.register_command :crash
    use_ui @ui do
      assert_raise Gem::MockGemUi::TermError do
        @command_manager.run %w[crash]
      end
      assert_equal "", ui.output
      err = ui.error.split("\n").first
      assert_equal "ERROR:  Loading command: crash (RuntimeError)", err
    end
  ensure
    $:.replace old_load_path
    @command_manager.unregister_command :crash
  end

  def test_process_args_with_c_flag
    custom_start_point = File.join @tempdir, "nice_folder"
    FileUtils.mkdir_p custom_start_point

    execution_path = nil
    use_ui @ui do
      @command_manager[:install].when_invoked do
        execution_path = Dir.pwd
        true
      end
      @command_manager.process_args %W[-C #{custom_start_point} install net-scp-4.0.0.gem --local]
    end

    assert_equal custom_start_point, execution_path
  end

  def test_process_args_with_c_flag_without_path
    use_ui @ui do
      assert_raise Gem::MockGemUi::TermError do
        @command_manager.process_args %w[-C install net-scp-4.0.0.gem --local]
      end
    end

    assert_match(/install isn't a directory./i, @ui.error)
  end

  def test_process_args_with_c_flag_path_not_found
    custom_start_point = File.join @tempdir, "nice_folder"
    FileUtils.mkdir_p custom_start_point
    custom_start_point.tr!("_", "-")

    use_ui @ui do
      assert_raise Gem::MockGemUi::TermError do
        @command_manager.process_args %W[-C #{custom_start_point} install net-scp-4.0.0.gem --local]
      end
    end

    assert_match(/#{custom_start_point} isn't a directory./i, @ui.error)
  end

  def test_process_args_bad_arg
    use_ui @ui do
      assert_raise Gem::MockGemUi::TermError do
        @command_manager.process_args %w[--bad-arg]
      end
    end

    assert_match(/invalid option: --bad-arg/i, @ui.error)
  end

  # HACK move to install command test
  def test_process_args_install
    #capture all install options
    use_ui @ui do
      check_options = nil
      @command_manager["install"].when_invoked do |options|
        check_options = options
        true
      end

      #check defaults
      @command_manager.process_args %w[install]
      assert_equal %w[ri], check_options[:document].sort
      assert_equal false, check_options[:force]
      assert_equal :both, check_options[:domain]
      assert_equal true, check_options[:wrappers]
      assert_equal Gem::Requirement.default, check_options[:version]
      assert_nil   check_options[:install_dir]
      assert_nil   check_options[:bin_dir]

      #check settings
      check_options = nil
      @command_manager.process_args %w[
        install --force --local --document=ri,rdoc --install-dir .
        --version 3.0 --no-wrapper --bindir .
      ]
      assert_equal %w[rdoc ri], check_options[:document].sort
      assert_equal true, check_options[:force]
      assert_equal :local, check_options[:domain]
      assert_equal false, check_options[:wrappers]
      assert_equal Gem::Requirement.new("3.0"), check_options[:version]
      assert_equal Dir.pwd, check_options[:install_dir]
      assert_equal Dir.pwd, check_options[:bin_dir]

      #check remote domain
      check_options = nil
      @command_manager.process_args %w[install --remote]
      assert_equal :remote, check_options[:domain]

      #check both domain
      check_options = nil
      @command_manager.process_args %w[install --both]
      assert_equal :both, check_options[:domain]

      #check both domain
      check_options = nil
      @command_manager.process_args %w[install --both]
      assert_equal :both, check_options[:domain]
    end
  end

  # HACK move to uninstall command test
  def test_process_args_uninstall
    #capture all uninstall options
    check_options = nil
    @command_manager["uninstall"].when_invoked do |options|
      check_options = options
      true
    end

    #check defaults
    @command_manager.process_args %w[uninstall]
    assert_equal Gem::Requirement.default, check_options[:version]

    #check settings
    check_options = nil
    @command_manager.process_args %w[uninstall foobar --version 3.0]
    assert_equal "foobar", check_options[:args].first
    assert_equal Gem::Requirement.new("3.0"), check_options[:version]
  end

  # HACK move to check command test
  def test_process_args_check
    #capture all check options
    check_options = nil
    @command_manager["check"].when_invoked do |options|
      check_options = options
      true
    end

    #check defaults
    @command_manager.process_args %w[check]
    assert_equal true, check_options[:alien]

    #check settings
    check_options = nil
    @command_manager.process_args %w[check foobar --alien]
    assert_equal true, check_options[:alien]
  end

  # HACK move to build command test
  def test_process_args_build
    #capture all build options
    check_options = nil
    @command_manager["build"].when_invoked do |options|
      check_options = options
      true
    end

    #check defaults
    @command_manager.process_args %w[build]
    #NOTE: Currently no defaults

    #check settings
    check_options = nil
    @command_manager.process_args %w[build foobar.rb]
    assert_equal "foobar.rb", check_options[:args].first
  end

  # HACK move to query command test
  def test_process_args_query
    #capture all query options
    check_options = nil
    @command_manager["query"].when_invoked do |options|
      check_options = options
      true
    end

    #check defaults
    Gem::Deprecate.skip_during do
      @command_manager.process_args %w[query]
    end
    assert_nil(check_options[:name])
    assert_equal :local, check_options[:domain]
    assert_equal false, check_options[:details]

    #check settings
    check_options = nil
    Gem::Deprecate.skip_during do
      @command_manager.process_args %w[query --name foobar --local --details]
    end
    assert_equal(/foobar/i, check_options[:name])
    assert_equal :local, check_options[:domain]
    assert_equal true, check_options[:details]

    #remote domain
    check_options = nil
    Gem::Deprecate.skip_during do
      @command_manager.process_args %w[query --remote]
    end
    assert_equal :remote, check_options[:domain]

    #both (local/remote) domains
    check_options = nil
    Gem::Deprecate.skip_during do
      @command_manager.process_args %w[query --both]
    end
    assert_equal :both, check_options[:domain]
  end

  # HACK move to update command test
  def test_process_args_update
    #capture all update options
    check_options = nil
    @command_manager["update"].when_invoked do |options|
      check_options = options
      true
    end

    #check defaults
    @command_manager.process_args %w[update]
    assert_includes check_options[:document], "ri"

    #check settings
    check_options = nil
    @command_manager.process_args %w[update --force --document=ri --install-dir .]
    assert_includes check_options[:document], "ri"
    assert_equal true, check_options[:force]
    assert_equal Dir.pwd, check_options[:install_dir]
  end

  def test_deprecated_command
    require "rubygems/command"
    foo_command = Class.new(Gem::Command) do
      extend Gem::Deprecate

      rubygems_deprecate_command

      def execute
        say "pew pew!"
      end
    end

    Gem::Commands.send(:const_set, :FooCommand, foo_command)
    @command_manager.register_command(:foo, foo_command.new("foo"))

    use_ui @ui do
      @command_manager.process_args(%w[foo])
    end

    assert_equal "pew pew!\n", @ui.output
    assert_match(/WARNING:  foo command is deprecated. It will be removed in Rubygems [0-9]+/, @ui.error)
  ensure
    Gem::Commands.send(:remove_const, :FooCommand)
  end

  def test_deprecated_command_with_version
    require "rubygems/command"
    foo_command = Class.new(Gem::Command) do
      extend Gem::Deprecate

      rubygems_deprecate_command("9.9.9")

      def execute
        say "pew pew!"
      end
    end

    Gem::Commands.send(:const_set, :FooCommand, foo_command)
    @command_manager.register_command(:foo, foo_command.new("foo"))

    use_ui @ui do
      @command_manager.process_args(%w[foo])
    end

    assert_equal "pew pew!\n", @ui.output
    assert_match(/WARNING:  foo command is deprecated. It will be removed in Rubygems 9.9.9/, @ui.error)
  ensure
    Gem::Commands.send(:remove_const, :FooCommand)
  end
end
