require 'test/unit'
require 'mocha/test_unit'

begin
  gem 'net-ssh', '>= 2.0.0'
  require 'net/ssh'
rescue LoadError
  $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../net-ssh/lib"

  begin
    require 'net/ssh'
    require 'net/ssh/version'
    raise LoadError, 'wrong version' unless Net::SSH::Version::STRING >= '1.99.0'
  rescue LoadError => e
    abort "could not load net/ssh v2 (#{e.inspect})"
  end
end

$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"

require 'net/scp'
require 'net/ssh/test'

class Net::SSH::Test::Channel
  def gets_ok
    gets_data "\0"
  end

  def sends_ok
    sends_data "\0"
  end
end

class Net::SCP::TestCase < Test::Unit::TestCase
  include Net::SSH::Test

  def default_test
    # do nothing, this is just a hacky-hack to work around Test::Unit's
    # insistence that all TestCase subclasses have at least one test
    # method defined.
  end

  protected

  def prepare_file(path, contents = '', mode = 0o0666, mtime = Time.now, atime = Time.now)
    entry = FileEntry.new(path, contents, mode, mtime, atime)
    entry.stub!
    entry
  end

  def prepare_directory(path, mode = 0o0777, mtime = Time.now, atime = Time.now)
    directory = DirectoryEntry.new(path, mode, mtime, atime)
    yield directory if block_given?
    directory.stub!
  end

  # The POSIX spec unfortunately allows all characters in file names except
  # ASCII 0x00(NUL) and 0x2F(/)
  #
  # Ideally, we should be testing filenames with newlines, but Mocha doesn't
  # like this at all, so we leave them out. However, the Shellwords module
  # handles newlines just fine, so we can be reasonably confident that they
  # will work in practice
  def awful_file_name
    (((0x00..0x7f).to_a - [0x00, 0x0a, 0x2b, 0x2f]).map { |n| n.chr }).join + '.txt'
  end

  def escaped_file_name
    "\\\001\\\002\\\003\\\004\\\005\\\006\\\a\\\b\\\t\\\v\\\f\\\r\\\016\\\017\\\020\\\021\\\022\\\023\\\024\\\025" \
      "\\\026\\\027\\\030\\\031\\\032\\\e\\\034\\\035\\\036\\\037\\ \\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*,-.0123456789:" \
      "\\;\\<\\=\\>\\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\[\\\\\\]\\^_\\`abcdefghijklmnopqrstuvwxyz\\{\\|\\}\\~\\\177.txt"
  end

  class FileEntry
    attr_reader :path, :contents, :mode, :mtime, :atime, :io

    def initialize(path, contents, mode = 0o0666, mtime = Time.now, atime = Time.now)
      @path = path
      @contents = contents
      @mode = mode
      @mtime = mtime
      @atime = atime
    end

    def name
      @name ||= File.basename(path)
    end

    def stub!
      # Stub for File::Stat
      stat = Object.new
      stat.stubs(size: contents.length, mode: mode, mtime: mtime, atime: atime, directory?: false)

      File.stubs(:stat).with(path).returns(stat)
      File.stubs(:directory?).with(path).returns(false)
      File.stubs(:file?).with(path).returns(true)
      File.stubs(:open).with(path, 'rb').returns(StringIO.new(contents))

      @io = StringIO.new
      File.stubs(:new).with(path, 'wb', mode).returns(io)
    end
  end

  class DirectoryEntry
    attr_reader :path, :mode, :mtime, :atime, :entries

    def initialize(path, mode = 0o0777, mtime = Time.now, atime = Time.now)
      @path = path
      @mode = mode
      @mtime = mtime
      @atime = atime
      @entries = []
    end

    def name
      @name ||= File.basename(path)
    end

    def file(name, *args)
      (entries << FileEntry.new(File.join(path, name), *args)).last
    end

    def directory(name, *args)
      entry = DirectoryEntry.new(File.join(path, name), *args)
      yield entry if block_given?
      (entries << entry).last
    end

    def stub!
      Dir.stubs(:mkdir).with { |*a| a.first == path }

      # Stub for File::Stat
      stat = Object.new
      stat.stubs(size: 1024, mode: mode, mtime: mtime, atime: atime, directory?: true)

      File.stubs(:stat).with(path).returns(stat)
      File.stubs(:directory?).with(path).returns(true)
      File.stubs(:file?).with(path).returns(false)
      Dir.stubs(:entries).with(path).returns(%w[. ..] + entries.map { |e| e.name }.sort)

      entries.each { |e| e.stub! }
    end
  end

  def expect_scp_session(arguments)
    story do |session|
      channel = session.opens_channel
      channel.sends_exec "scp #{arguments}"
      yield channel if block_given?
      channel.sends_eof
      channel.gets_exit_status
      channel.gets_eof
      channel.gets_close
      channel.sends_close
    end
  end

  def scp(options = {})
    @scp ||= Net::SCP.new(connection(options))
  end
end
