require 'pathname'

# rubocop:disable BlockLength
describe WinRM::FS::FileManager do
  let(:upload_dir) { "winrm_#{rand(2**16)}" }
  let(:dest_dir) { File.join(subject.temp_dir, upload_dir) }
  let(:temp_upload_dir) { '$env:TEMP/winrm-upload' }
  let(:spec_dir) { File.expand_path(File.dirname(File.dirname(__FILE__))) }
  let(:this_file) { Pathname.new(__FILE__) }
  let(:service) { winrm_connection }

  subject { WinRM::FS::FileManager.new(service) }

  before(:each) do
    expect(subject.delete(dest_dir)).to be true
    expect(subject.delete(temp_upload_dir)).to be true
  end

  context 'exists?' do
    it 'should exist' do
      expect(subject.exists?('c:/windows')).to be true
      expect(subject.exists?('c:/foobar')).to be false
    end
  end

  context 'create and delete dir' do
    it 'should create the directory recursively' do
      subdir = File.join(dest_dir, 'subdir1', 'subdir2')
      expect(subject.create_dir(subdir)).to be true
      expect(subject.exists?(subdir)).to be true
      expect(subject.create_dir(subdir)).to be true
      expect(subject.delete(subdir)).to be true
      expect(subject.exists?(subdir)).to be false
    end
  end

  context 'temp_dir' do
    it 'should return the remote users temp dir' do
      expect(subject.temp_dir).to match(%r{C:/Users/\S+/AppData/Local/Temp})
    end
  end

  context 'download file' do
    let(:download_dir) { File.join(spec_dir, 'temp') }
    let(:dest_file) { Pathname.new(File.join(dest_dir, File.basename(this_file))) }
    let(:download_file) { Pathname.new(File.join(download_dir, File.basename(this_file))) }

    before(:each) do
      expect(subject.delete(dest_dir)).to be true
      FileUtils.rm_rf(Dir.glob("#{download_dir}/*"))
      FileUtils.mkdir_p(download_dir)
    end

    it 'should download the specified file' do
      subject.upload(this_file, dest_file)
      subject.download(dest_file, download_file)
      expect(File.open(download_file).read).to eq(File.open(this_file).read)
    end
  end

  context 'download directory' do
    let(:download_dir) { File.join(spec_dir, 'temp') }
    let(:dest_file) { Pathname.new(File.join(dest_dir, File.basename(this_file))) }
    let(:root_dir) { File.expand_path('../../', File.dirname(__FILE__)) }
    let(:winrm_fs_dir) { File.join(root_dir, 'lib/winrm-fs') }

    before(:each) do
      expect(subject.delete(dest_dir)).to be true
      FileUtils.rm_rf(Dir.glob("#{download_dir}/*"))
      FileUtils.mkdir_p(download_dir)
    end

    it 'downloads the directory contents recursively' do
      subject.upload(winrm_fs_dir, dest_dir)
      subject.download(dest_dir, download_dir)

      Dir.glob(winrm_fs_dir + '/**/*.rb').each do |host_file|
        host_file_rel = Pathname.new(host_file).relative_path_from(
          Pathname.new(winrm_fs_dir)
        ).to_s
        download_file = File.join(download_dir, upload_dir, host_file_rel)
        expect(File.open(download_file).read).to eq(File.open(host_file).read)
      end
    end
  end

  context 'upload file' do
    let(:dest_file) { Pathname.new(File.join(dest_dir, File.basename(this_file))) }

    before(:each) do
      expect(subject.delete(dest_dir)).to be true
    end

    it 'should upload the specified file' do
      subject.upload(this_file, dest_file)
      expect(subject).to have_created(dest_file).with_content(this_file)
    end

    it 'should upload to root of the c: drive' do
      subject.upload(this_file, 'c:/winrmtest.rb')
      expect(subject).to have_created('c:/winrmtest.rb').with_content(this_file)
      subject.delete('c:/winrmtest.rb')
    end

    it 'should upload just filename' do
      subject.upload(this_file, 'winrmtest.rb')
      expect(subject).to have_created('winrmtest.rb').with_content(this_file)
      subject.delete('winrmtest.rb')
    end

    it 'should upload using relative file path' do
      subject.upload('./spec/integration/file_manager_spec.rb', dest_file)
      expect(subject).to have_created(dest_file).with_content(this_file)
    end

    it 'should upload to the specified directory' do
      subject.create_dir(dest_dir)
      subject.upload(this_file, dest_dir)
      expect(subject).to have_created(dest_file).with_content(this_file)
    end

    it 'should treat extensionless target as file if not an existing directory' do
      subject.upload(this_file, dest_dir)
      expect(subject).to have_created(dest_dir).with_content(this_file)
    end

    it 'should create extensionless source under target dir if target dir exists' do
      subject.create_dir(dest_dir)
      src_file = File.expand_path('../../Gemfile', File.dirname(__FILE__))
      subject.upload(src_file, dest_dir)
      expect(subject).to have_created(File.join(dest_dir, 'Gemfile')).with_content(src_file)
    end

    it 'should upload to the specified directory with env var' do
      subject.upload(this_file, '$env:Temp')
      expected_dest_file = File.join(subject.temp_dir, File.basename(this_file))
      expect(subject).to have_created(expected_dest_file).with_content(this_file)
    end

    it 'should upload to Program Files sub dir' do
      subject.create_dir('$env:ProgramFiles/foo')
      subject.upload(this_file, '$env:ProgramFiles/foo')
      expect(subject).to have_created('c:/Program Files/foo/file_manager_spec.rb') \
        .with_content(this_file)
    end

    it 'should upload to the specified nested directory' do
      dest_sub_dir = File.join(dest_dir, 'subdir')
      subject.create_dir(dest_sub_dir)
      dest_sub_dir_file = File.join(dest_sub_dir, File.basename(this_file))
      subject.upload(this_file, dest_sub_dir)
      expect(subject).to have_created(dest_sub_dir_file).with_content(this_file)
    end

    it 'yields progress data' do
      block_called = false
      total_bytes_copied = 0
      total = subject.upload(this_file, dest_file) do |bytes_copied, total_bytes, local_path, remote_path|
        expect(total_bytes).to be > 0
        total_bytes_copied = bytes_copied
        expect(local_path).to eq(this_file.to_s)
        expect(remote_path).to eq(dest_file.to_s)
        block_called = true
      end
      expect(total_bytes_copied).to eq(total)
      expect(block_called).to be true
      expect(total).to be > 0
    end

    it 'should not upload when content matches' do
      subject.upload(this_file, dest_dir)
      bytes_uploaded = subject.upload(this_file, dest_dir)
      expect(bytes_uploaded).to eq 0
    end

    it 'should upload when content differs' do
      matchers_file = File.join(spec_dir, 'matchers.rb')
      subject.upload(matchers_file, dest_file)
      bytes_uploaded = subject.upload(this_file, dest_file)
      expect(bytes_uploaded).to be > 0
    end

    it 'raises WinRMUploadError when a bad source path is specified' do
      expect { subject.upload('c:/some/non-existant/path/foo', dest_file) }.to raise_error
    end
  end

  context 'upload empty file' do
    let(:empty_src_file) { Tempfile.new('empty') }
    let(:dest_file) { File.join(dest_dir, 'emptyfile.txt') }

    it 'creates a new empty file' do
      expect(subject.upload(empty_src_file.path, dest_file)).to be 0
      expect(subject).to have_created(dest_file).with_content('')
    end

    it 'overwrites an existing file' do
      expect(subject.upload(this_file, dest_file)).to be > 0
      expect(subject.upload(empty_src_file.path, dest_file)).to be 0
      expect(subject).to have_created(dest_file).with_content('')
    end
  end

  context 'upload directory' do
    let(:root_dir) { File.expand_path('../../', File.dirname(__FILE__)) }
    let(:winrm_fs_dir) { File.join(root_dir, 'lib/winrm-fs') }
    let(:core_dir) { File.join(root_dir, 'lib/winrm-fs/core') }
    let(:scripts_dir) { File.join(root_dir, 'lib/winrm-fs/scripts') }

    it 'copies the directory contents recursively when directory does not exist' do
      bytes_uploaded = subject.upload(winrm_fs_dir, dest_dir)
      expect(bytes_uploaded).to be > 0

      Dir.glob(winrm_fs_dir + '/**/*.rb').each do |host_file|
        host_file_rel = Pathname.new(host_file).relative_path_from(
          Pathname.new(winrm_fs_dir)
        ).to_s
        remote_file = File.join(dest_dir, host_file_rel)
        expect(subject).to have_created(remote_file).with_content(host_file)
      end
    end

    it 'copies the directory recursively when directory does exist' do
      subject.create_dir(dest_dir)
      bytes_uploaded = subject.upload(winrm_fs_dir, dest_dir)
      expect(bytes_uploaded).to be > 0

      Dir.glob(winrm_fs_dir + '/**/*.rb').each do |host_file|
        host_file_rel = Pathname.new(host_file).relative_path_from(
          Pathname.new(winrm_fs_dir).dirname
        ).to_s
        remote_file = File.join(dest_dir, host_file_rel)
        expect(subject).to have_created(remote_file).with_content(host_file)
      end
    end

    it 'does not copy the directory when content is the same' do
      subject.upload(winrm_fs_dir, dest_dir)
      bytes_uploaded = subject.upload(winrm_fs_dir, dest_dir)
      expect(bytes_uploaded).to eq 0
    end

    it 'unzips the directory when cached content is the same' do
      subject.upload(winrm_fs_dir, dest_dir)
      subject.delete(dest_dir)
      expect(subject.exists?(dest_dir)).to be false
      subject.upload(winrm_fs_dir, dest_dir)
      expect(subject.exists?(dest_dir)).to be true
    end

    it 'unzips multiple directories when cached content is the same for all' do
      subject.create_dir(dest_dir)
      subject.upload([core_dir, scripts_dir], dest_dir)
      subject.delete(dest_dir)
      expect(subject.exists?(dest_dir)).to be false
      subject.create_dir(dest_dir)
      subject.upload([core_dir, scripts_dir], dest_dir)
      expect(subject.exists?(File.join(dest_dir, 'core'))).to be true
      expect(subject.exists?(File.join(dest_dir, 'scripts'))).to be true
    end

    it 'copies the directory when content differs' do
      subject.upload(winrm_fs_dir, dest_dir)
      bytes_uploaded = subject.upload(core_dir, dest_dir)
      expect(bytes_uploaded).to be > 0
    end
  end
end
# rubocop:enable BlockLength