# frozen_string_literal: true

#
# Collect S3 Resources
#
class S3 < Mapper
  #
  # Returns an array of resources.
  #
  # Since S3 is a global service, the bucket operation calls
  # can be parallelized.
  #
  def collect
    resources = []

    #
    # list_buckets
    #
    @client.list_buckets.each_with_index do |response, page|
      log(response.context.operation_name, page)

      Parallel.map(response.buckets.each, in_threads: @options.threads) do |bucket|
        @thread = Parallel.worker_number
        log(response.context.operation_name, bucket.name)

        struct = OpenStruct.new(bucket)
        struct.type = 'bucket'
        struct.arn = "arn:aws:s3:::#{bucket.name}"

        # check bucket region constraint
        location = @client.get_bucket_location({ bucket: bucket.name }).location_constraint

        # if you use a region other than the us-east-1 endpoint
        # to create a bucket, you must set the location_constraint
        # bucket parameter to the same region. (https://docs.aws.amazon.com/general/latest/gr/s3.html)
        client = if location.empty?
                   struct.location = 'us-east-1'
                   @client
                 else
                   location = 'eu-west-1' if location == 'EU'
                   struct.location = location
                   Aws::S3::Client.new({ region: location })
                 end

        operations = [
          { func: 'get_bucket_acl', key: 'acl', field: nil },
          { func: 'get_bucket_encryption', key: 'encryption', field: 'server_side_encryption_configuration' },
          { func: 'get_bucket_replication', key: 'replication', field: 'replication_configuration' },
          { func: 'get_bucket_policy', key: 'policy', field: 'policy' },
          { func: 'get_bucket_policy_status', key: 'public', field: 'policy_status' },
          { func: 'get_public_access_block', key: 'public_access_block', field: 'public_access_block_configuration' },
          { func: 'get_object_lock_configuration', key: 'object_lock_configuration', field: 'object_lock_configuration' },
          { func: 'get_bucket_tagging', key: 'tagging', field: nil },
          { func: 'get_bucket_logging', key: 'logging', field: 'logging_enabled' },
          { func: 'get_bucket_versioning', key: 'versioning', field: nil },
          { func: 'get_bucket_website', key: 'website', field: nil },
          { func: 'get_bucket_ownership_controls', key: 'ownership_controls', field: 'ownership_controls' }
        ]

        operations.each do |operation|
          op = OpenStruct.new(operation)

          resp = client.send(op.func, { bucket: bucket.name })

          struct[op.key] = if op.key == 'policy'
                             resp.policy.string.parse_policy
                           else
                             op.field ? resp.send(op.field).to_h : resp.to_h
                           end

        rescue Aws::S3::Errors::ServiceError => e
          log_error(bucket.name, op.func, e.code)

          raise e unless suppressed_errors.include?(e.code) && !@options.quit_on_exception
        end

        resources.push(struct.to_h)

      rescue Aws::S3::Errors::NoSuchBucket
        # skip missing bucket
      end
    end

    resources
  end

  private

  # not an error
  def suppressed_errors
    %w[
      AccessDenied
      ServerSideEncryptionConfigurationNotFoundError
      NoSuchBucketPolicy
      NoSuchTagSet
      NoSuchWebsiteConfiguration
      ReplicationConfigurationNotFoundError
      NoSuchPublicAccessBlockConfiguration
      ObjectLockConfigurationNotFoundError
      OwnershipControlsNotFoundError
    ]
  end
end
