# frozen_string_literal: true

require 'base64'
require 'tempfile'
require 'fileutils'
require 'net/https'
require 'net/http/post/multipart'
require 'uri'
require 'stringio'

Before("@s3", "@resources") do
  @s3 = Aws::S3::Resource.new
  @client = @s3.client
  @created_buckets = []
end

After("@s3", "@resources") do
  @created_buckets.each do |bucket|
    bucket.delete!
  end
end

Given(/^I create a bucket resource$/) do
  @bucket_name = "aws-sdk-resources-#{Time.now.to_i}-#{rand(1000)}"
  @bucket = @s3.create_bucket(bucket: @bucket_name)
  @s3.client.wait_until(:bucket_exists, bucket: @bucket_name)
  @created_buckets << @bucket
end

Given("I have {int} {int}MB chunks") do |number, size|
  @chunks = number.to_i.times.map {'.' * size.to_i * 1024 * 1024}
end

Given("I have a {int}MB stream") do |size|
  @stream = StringIO.new('.' * size * 1024 * 1024)
end

When("I upload the stream with the custom part size of {int}MB to the {string} object") do |part_size_mb, key|
  @bucket.object(key).upload_stream(part_size: part_size_mb * 1024 * 1024) do |write_stream|
    IO.copy_stream(@stream, write_stream)
  end
end

Then("the {string} object should contained the stream") do |key|
  data = @s3.bucket(@bucket_name).object(key).get.body.read
  expect(data).to eq(@stream.string)
end

When(/^I upload the chunks using tempfile to the "(.*?)" object$/) do |key|
  @bucket.object(key).upload_stream(tempfile: true) do |write_stream|
    @chunks.each {|chunk| write_stream << chunk }
  end
end

When(/^I upload the chunks to the "(.*?)" object$/) do |key|
  @bucket.object(key).upload_stream do |write_stream|
    @chunks.each {|chunk| write_stream << chunk }
  end
end

When(/^I upload the chunks to the "(.*?)" object with SSE\/CPK$/) do |key|
  require 'openssl'
  cipher = OpenSSL::Cipher::AES256.new(:CBC)
  encryption_key = cipher.random_key
  @bucket.object(key).upload_stream({
    sse_customer_key: encryption_key,
    sse_customer_algorithm: 'AES256'
  }) do |write_stream|
    @chunks.each {|chunk| write_stream << chunk }
  end
end

Then(/^the chunks should have been uploaded as a multipart upload$/) do
  expect(ApiCallTracker.called_operations).to include(:create_multipart_upload)
  expect(ApiCallTracker.called_operations).to include(:upload_part)
  expect(ApiCallTracker.called_operations).to include(:complete_multipart_upload)
end

Then(/the "(.*?)" object should contained the chunks joined/) do |key|
  data = @s3.bucket(@bucket_name).object(key).get.body.read
  expect(data).to eq(@chunks.join)
end


Given(/^I have a (\d+)MB file$/) do |size|
  @file = Tempfile.new('tempfile')
  @file.write('.' * size.to_i * 1024 * 1024)
  @file.rewind
end

When(/^I upload the file to the "(.*?)" object$/) do |key|
  @bucket.object(key).upload_file(@file)
end

When(/^I upload the file$/) do
  @object = @bucket.object(@file.path)
  @object.upload_file(@file)
  @old_key = @file.path
end

When(/^I upload the file to the "(.*?)" object with SSE\/CPK$/) do |key|
  require 'openssl'
  cipher = OpenSSL::Cipher::AES256.new(:CBC)
  ecnryption_key = cipher.random_key
  @bucket.object(key).upload_file(@file, {
    sse_customer_key: ecnryption_key,
    sse_customer_algorithm: 'AES256'
  })
  @bucket.object(key).upload_file(@file)
end

Then(/^the file should have been uploaded as a multipart upload$/) do
  expect(ApiCallTracker.called_operations).to include(:create_multipart_upload)
end

Given(/^I have an encryption client$/) do
  @cse = Aws::S3::Encryption::Client.new({
    client: @s3.client,
    encryption_key: Base64.decode64("w1WLio3agRWRTSJK/Ouh8NHoqRQ6fn5WbSXDTHjXMSo="),
  })
end

Given(/^I have a V2 encryption client$/) do
  @cse = Aws::S3::EncryptionV2::Client.new(
   client: @s3.client,
   encryption_key: Base64.decode64("w1WLio3agRWRTSJK/Ouh8NHoqRQ6fn5WbSXDTHjXMSo="),
   key_wrap_schema: :aes_gcm,
   content_encryption_schema: :aes_gcm_no_padding,
   security_profile: :v2_and_legacy
  )
end

Given(/^I have an encryption client configured to use KMS$/) do
  @cse = Aws::S3::Encryption::Client.new({
    client: @s3.client,
    kms_key_id: @kms_key_id,
  })
end

Given(/^I have a V2 encryption client configured to use KMS$/) do
  @cse = Aws::S3::EncryptionV2::Client.new(
   client: @s3.client,
   kms_key_id: @kms_key_id
  )
end

Given(/^I have an encryption client configured for :instruction_file$/) do
  @cse = Aws::S3::Encryption::Client.new({
    client: @s3.client,
    encryption_key: Base64.decode64("w1WLio3agRWRTSJK/Ouh8NHoqRQ6fn5WbSXDTHjXMSo="),
    envelope_location: :instruction_file,
  })
end

Given(/^I have a V2 encryption client configured for :instruction_file$/) do
  @cse = Aws::S3::EncryptionV2::Client.new(
   client: @s3.client,
   encryption_key: Base64.decode64("w1WLio3agRWRTSJK/Ouh8NHoqRQ6fn5WbSXDTHjXMSo="),
   envelope_location: :instruction_file,
   key_wrap_schema: :aes_gcm,
   content_encryption_schema: :aes_gcm_no_padding,
   security_profile: :v2_and_legacy
  )
end

When(/^I perform an encrypted PUT of the value "(.*?)"$/) do |value|
  @key = 'encrypted'
  @plain_text = value
  @cse.put_object(bucket: @bucket_name, key: @key, body: @plain_text)
end

When(/^I GET the object with a non\-encryption client$/) do
  @cipher_text = @s3.client.get_object(bucket: @bucket_name, key: @key).body.read
end

When(/^I GET the object with a V2 encryption client$/) do
  cse_v2 = Aws::S3::EncryptionV2::Client.new(
    client: @s3.client,
    encryption_key: Base64.decode64("w1WLio3agRWRTSJK/Ouh8NHoqRQ6fn5WbSXDTHjXMSo="),
    key_wrap_schema: :aes_gcm,
    content_encryption_schema: :aes_gcm_no_padding,
    security_profile: :v2_and_legacy
  )
  @cipher_text = cse_v2.get_object(bucket: @bucket_name, key: @key).body.read
end

Then(/^the object data should be encrypted$/) do
  expect(@cipher_text).not_to eq(@plain_text)
end

When(/^I GET the object with an encryption client$/) do
  @plain_text = @cse.get_object(bucket: @bucket_name, key: @key).body.read
end

Then(/^the object data should be "(.*?)"$/) do |value|
  expect(@plain_text).to eq(value)
end

Then(/^the instruction file should exist$/) do
  expect {
    @s3.client.head_object(bucket: @bucket_name, key: @key + '.instruction')
  }.not_to raise_error
end

When(/^I create a presigned post$/) do
  @post = @bucket.object('key').presigned_post({
    success_action_status: '201',
  })
end

Then(/^I should be able to POST an object to the form url$/) do
  uri = URI.parse(@post.url)
  req = Net::HTTP::Post::Multipart.new(uri.request_uri, @post.fields.merge(
    "file" => UploadIO.new(File.open(__FILE__, 'r'), 'text/plain')
  ))
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  resp = http.request(req)
  expect(resp.code.to_i).to eq(201)
end

Given(/^I have an encryption client configured to read a Java encrypted object$/) do
  @cse = Aws::S3::Encryption::Client.new(
    profile: @profile,
    kms_key_id: @kms_key_id,
  )
end
Given(/^I have a V2 encryption client configured to read a Java encrypted object$/) do
  @cse = Aws::S3::EncryptionV2::Client.new(
    profile: @profile,
    kms_key_id: @kms_key_id,
    )
end

Then(/^I should be able to multipart copy the object to a different bucket$/) do
  target_bucket = @s3.bucket(@bucket_name)
  target_object = target_bucket.object("#{@source_key}-copy")
  target_object.copy_from("#{@source_bucket}/#{@source_key}", multipart_copy: true)
  expect(ApiCallTracker.called_operations).to include(:create_multipart_upload)
end

Then(/^I should be able to multipart copy the object$/) do
  target_bucket = @s3.bucket(@bucket_name)
  target_object = target_bucket.object("test object-copy")
  target_object.copy_from("#{@bucket_name}/test object", multipart_copy: true)
  expect(ApiCallTracker.called_operations).to include(:create_multipart_upload)
end

Given(/^I have a (\d+)M file$/) do |mb|
  @file = Tempfile.new('randomfile')
  File.open(@file, 'wb') {
    |f| f.write(Random.new.bytes(mb.to_i * 1024 * 1024))}
end

Given(/^I upload the file using put_object$/) do
  @object = @s3.bucket(@bucket_name).object(@file.path)
  @object.put(body: @file)
end

Then(/^(\d+) (\w+) requests? should have been made$/) do |expected_count, method_name|
  expect(
    ApiCallTracker.called_operations.count { |name| method_name.to_sym == name }
  ).to eq(expected_count.to_i)
end

Then(/^the downloaded file should match the uploaded file$/) do
  expect(FileUtils.compare_file(@file.path, @download_file_dest)).to be(true)
end

When(/^I download the file with mode "([^"]*)"$/) do |mode|
  # create a temp directory for download
  tempfile = Tempfile.new("sample")
  @download_file_dest = tempfile.path
  tempfile.unlink

  @object.download_file(@download_file_dest, mode: mode)
end

When(/^I download the file with mode "([^"]*)" with (\d+)M chunk size$/) do |mode, mb|
  tempfile = Tempfile.new("sample")
  @download_file_dest = tempfile.path
  tempfile.unlink

  @object.download_file(@download_file_dest, mode: mode, chunk_size: (mb.to_i * 1024 *1024))
end

Then(/^this test file has been cleaned up$/) do
  File.unlink(@download_file_dest)
  expect(File.exist?(@download_file_dest)).to be(false)
end

When(/^I download the file (\d+) times with mode "([^"]*)" with (\d+)M chunk size$/) do |cnt, mode, mb|
  @download_dest = []
  (1..cnt.to_i).each do |e|
    tempfile = Tempfile.new("sample_#{e}")
    @download_dest << tempfile.path
    tempfile.unlink
  end

  (1..cnt.to_i)
    .map { |c| Thread.new { @object.download_file(@download_dest[c - 1], mode: mode, chunk_size: (mb.to_i * 1024 *1024)) } }
    .each(&:join)
end

Then(/^those downloaded files should match the uploaded file$/) do
  @download_dest.each do |download|
    expect(FileUtils.compare_file(@file.path, download)).to be(true)
  end
end

Then(/^these test file has been cleaned up$/) do
  @download_dest.each do |file|
    File.unlink(file)
    expect(File.exist?(file)).to be(false)
  end
end

Given(/^I put "([^"]*)" to the object with key "([^"]*)"$/) do |body, key|
  @obj = @bucket.object(key)
  @obj.put(body: body)
end

Then(/^I can streaming download object with key "([^"]*)"$/) do |key|
  resp = @obj.get do |chunk|
    expect(chunk).to eq("hello world")
  end
  expect(resp.body).to be_a(Aws::S3::Plugins::RetryableBlockIO)
end

Given(/^I enabled bucket versioning$/) do
  @s3.client.put_bucket_versioning(
    bucket: @bucket_name,
    versioning_configuration: {
      status: "Enabled"
    }
  )
end

Given(/^I upload the file with same key$/) do
  @version_id = @object.version_id
  @object = @bucket.object(@old_key)
  @object.upload_file(@file)
end

When(/^I download the file with previous version id$/) do
  tempfile = Tempfile.new("sample")
  @download_file_dest = tempfile.path
  tempfile.unlink

  @object.download_file(@download_file_dest, version_id: @version_id)
end

Then(/^the download file should match the previous version object$/) do
  expect(FileUtils.compare_file(@old_key, @download_file_dest)).to be(true)
end
