require_relative 'common'
require 'net/ssh/buffer'
require 'net/ssh'
require 'timeout'
require 'tempfile'
require 'fileutils'
require 'net/ssh/proxy/command'
require 'net/ssh/proxy/jump'

class TestProxy < NetSSHTest
  include IntegrationTestHelpers

  def localhost
    'localhost'
  end

  def user
    'net_ssh_1'
  end

  def ssh_start_params(options)
    [localhost, user, { keys: @key_id_rsa }.merge(options)]
  end

  def setup_ssh_env(&block)
    tmpdir do |dir|
      @key_id_rsa = "#{dir}/id_rsa"
      ssh_keygen @key_id_rsa, "rsa"
      set_authorized_key(user, "#{@key_id_rsa}.pub")
      yield
    end
  end

  def setup_gateway(&block)
    gwhost = "gateway.netssh"
    gwuser = 'net_ssh_2'
    tmpdir do |dir|
      @gwkey_id_rsa = "#{dir}/id_rsa"
      ssh_keygen @gwkey_id_rsa, "rsa"
      set_authorized_key(gwuser, "#{@gwkey_id_rsa}.pub")
      config = "Host #{gwhost}
                  IdentityFile #{@gwkey_id_rsa}
                  StrictHostKeyChecking no
               "
      FileUtils.mkdir_p File.expand_path("~/.ssh")
      my_config = File.expand_path("~/.ssh/config")
      File.open(my_config, 'w') { |file| file.write(config) }
      begin
        FileUtils.chmod(0o600, my_config)
        yield gwuser, gwhost
      ensure
        FileUtils.rm(my_config)
      end
    end
  end

  def test_smoke
    setup_ssh_env do
      proxy = Net::SSH::Proxy::Command.new("/bin/nc localhost 22")
      msg = 'echo123'
      ret = Net::SSH.start(*ssh_start_params(proxy: proxy)) do |ssh|
        ssh.exec! "echo \"$USER:#{msg}\""
      end
      assert_equal "net_ssh_1:#{msg}\n", ret
    end
  end

  def with_spurious_write_wakeup_emulate(rate = 99, &block)
    orig_io_select = IO.method(:select)
    count = 0

    IO.singleton_class.send(:define_method, :select) do |*params|
      count += 1
      if (count % rate != 0)
        if params && params[1] && !params[1].empty?
          return [[], params[1], []]
        end
      end
      orig_io_select.call(*params)
    end

    begin
      yield
    ensure
      IO.singleton_class.send(:define_method, :select, &orig_io_select)
    end
  end

  def test_with_rate_limit_and_spurious_wakeup
    system("sudo sh -c 'echo 4096 > /proc/sys/fs/pipe-max-size'")
    begin
      setup_ssh_env do
        proxy = Net::SSH::Proxy::Command.new("/usr/bin/pv --rate-limit 100k | /bin/nc localhost 22")

        large_msg = 'echo123' * 30000

        ok = Net::SSH.start(*ssh_start_params(proxy: proxy)) do |ssh|
          with_spurious_write_wakeup_emulate do
            ret = ssh.exec! "echo \"$USER:#{large_msg}\""
            assert_match(%r{/bin/.*sh: Argument list too long\n}, ret)
            hello_count = 1000
            ret = ssh.exec! "ruby -e 'puts \"Hello\"*#{hello_count}'"
            assert_equal "Hello" * hello_count + "\n", ret
          end
          :ok
        end

        assert_equal :ok, ok
      end
    ensure
      system("sudo sh -c 'echo 1048576 > /proc/sys/fs/pipe-max-size'")
    end
  end

  def test_proxy_jump_through_localhost
    setup_ssh_env do
      setup_gateway do |gwuser, gwhost|
        proxy = Net::SSH::Proxy::Jump.new("#{gwuser}@#{gwhost}")

        output = Net::SSH.start(*ssh_start_params(proxy: proxy)) do |ssh|
          ssh.exec! "echo \"$USER:echo123\""
        end
        assert_equal "net_ssh_1:echo123\n", output
      end
    end
  end

  class DbgProxy
    attr_reader :io

    def initialize(origin)
      @origin = origin
    end

    def open(*args)
      @io = @origin.open(*args)
      @io
    end
  end

  def test_does_close_proxy_on_proxy_failure
    setup_ssh_env do
      proxy = DbgProxy.new(Net::SSH::Proxy::Command.new('sleep 2 && ssh -W %h:%p -o "PreferredAuthentications none" user@localhost'))
      msg = 'echo123'
      assert_raises Errno::EPIPE, Net::SSH::Proxy::ConnectError do
        Net::SSH.start(*ssh_start_params(proxy: proxy, password: 'bad', non_interactive: true, auth_methods: ['password'], verbose: :debug)) do |ssh|
          ssh.exec! "echo \"$USER:#{msg}\""
        end
      end
      assert proxy.io.nil? || proxy.io.closed?, "Process #proxy.io.pid not closed"
    end
  end
end
