require 'cloud_controller/blobstore/retryable_blob'

module CloudController
  module Blobstore
    class RetryableClient
      extend Forwardable

      def initialize(client:, errors:, logger:, num_retries: 3)
        @wrapped_client   = client
        @retryable_errors = errors
        @logger = logger
        @num_retries = num_retries
      end

      def_delegators :@wrapped_client,
                     :local?,
                     :root_dir

      def exists?(key)
        with_retries(__method__.to_s, {
                       args: {
                         key:
                       }
                     }) do
          @wrapped_client.exists?(key)
        end
      end

      def download_from_blobstore(source_key, destination_path, mode: nil)
        with_retries(__method__.to_s, {
                       args: {
                         source_key:,
                         destination_path:,
                         mode:
                       }
                     }) do
          @wrapped_client.download_from_blobstore(source_key, destination_path, mode:)
        end
      end

      def cp_to_blobstore(source_path, destination_key)
        with_retries(__method__.to_s, {
                       args: {
                         source_path:,
                         destination_key:
                       }
                     }) do
          @wrapped_client.cp_to_blobstore(source_path, destination_key)
        end
      end

      def cp_r_to_blobstore(source_dir)
        with_retries(__method__.to_s, {
                       args: {
                         source_dir:
                       }
                     }) do
          @wrapped_client.cp_r_to_blobstore(source_dir)
        end
      end

      def cp_file_between_keys(source_key, destination_key)
        with_retries(__method__.to_s, {
                       args: {
                         source_key:,
                         destination_key:
                       }
                     }) do
          @wrapped_client.cp_file_between_keys(source_key, destination_key)
        end
      end

      def delete_all(page_size=FogClient::DEFAULT_BATCH_SIZE)
        with_retries(__method__.to_s, {
                       args: {
                         page_size:
                       }
                     }) do
          @wrapped_client.delete_all(page_size)
        end
      end

      def delete_all_in_path(path)
        with_retries(__method__.to_s, {
                       args: {
                         path:
                       }
                     }) do
          @wrapped_client.delete_all_in_path(path)
        end
      end

      def delete(key)
        with_retries(__method__.to_s, {
                       args: {
                         key:
                       }
                     }) do
          @wrapped_client.delete(key)
        end
      end

      def delete_blob(blob)
        with_retries(__method__.to_s, {
                       args: {
                         blob:
                       }
                     }) do
          @wrapped_client.delete_blob(blob)
        end
      end

      def blob(key)
        with_retries(__method__.to_s, {
                       args: {
                         key:
                       }
                     }) do
          blob = @wrapped_client.blob(key)
          RetryableBlob.new(blob: blob, errors: @retryable_errors, logger: @logger, num_retries: @num_retries) if blob
        end
      end

      def files_for(prefix, ignored_directory_prefixes=[])
        with_retries(__method__.to_s, {
                       args: {
                         prefix:,
                         ignored_directory_prefixes:
                       }
                     }) do
          @wrapped_client.files_for(prefix, ignored_directory_prefixes)
        end
      end

      def ensure_bucket_exists
        with_retries(__method__.to_s, {}) do
          @wrapped_client.ensure_bucket_exists
        end
      end

      private

      def with_retries(log_prefix, log_data)
        retries ||= @num_retries
        yield
      rescue *@retryable_errors => e
        retries -= 1

        @logger.debug("#{log_prefix}-retry",
                      {
                        error: e.message,
                        remaining_retries: retries
                      }.merge(log_data))
        retry unless retries == 0
        raise e
      end
    end
  end
end
