require "backup/cloud_io/cloud_files"

module Backup
  module Storage
    class CloudFiles < Base
      include Storage::Cycler
      class Error < Backup::Error; end

      ##
      # Rackspace CloudFiles Credentials
      attr_accessor :username, :api_key

      ##
      # Rackspace Auth URL (optional)
      attr_accessor :auth_url

      ##
      # Rackspace Service Net
      # (LAN-based transfers to avoid charges and improve performance)
      attr_accessor :servicenet

      ##
      # Rackspace Region (optional)
      attr_accessor :region

      ##
      # Rackspace Container Name
      attr_accessor :container

      ##
      # Rackspace Container Name for SLO Segments
      # Required if #segment_size is set. Must be different from #container.
      attr_accessor :segments_container

      ##
      # SLO Segment size, specified in MiB.
      #
      # Each package file larger than +segment_size+
      # will be uploaded as a Static Large Objects (SLO).
      #
      # Defaults to 0 for backward compatibility (pre v.3.7.0),
      # since #segments_container would be required.
      #
      # Minimum: 1 (0 disables SLO support)
      # Maximum: 5120 (5 GiB)
      attr_accessor :segment_size

      ##
      # If set, all backup package files (including SLO segments) will be
      # scheduled for automatic removal by the server.
      #
      # The `keep` option should not be used if this is set,
      # unless you're transitioning from the `keep` option.
      attr_accessor :days_to_keep

      ##
      # Number of times to retry failed operations.
      #
      # Default: 10
      attr_accessor :max_retries

      ##
      # Time in seconds to pause before each retry.
      #
      # Default: 30
      attr_accessor :retry_waitsec

      ##
      # Additional options to pass along to fog.
      # e.g. Fog::Storage.new({ :provider => 'Rackspace' }.merge(fog_options))
      attr_accessor :fog_options

      def initialize(model, storage_id = nil)
        super

        @servicenet         ||= false
        @segment_size       ||= 0
        @max_retries        ||= 10
        @retry_waitsec      ||= 30

        @path ||= "backups"
        path.sub!(/^\//, "")

        check_configuration
      end

      private

      def cloud_io
        @cloud_io ||= CloudIO::CloudFiles.new(
          username: username,
          api_key: api_key,
          auth_url: auth_url,
          region: region,
          servicenet: servicenet,
          container: container,
          segments_container: segments_container,
          segment_size: segment_size,
          days_to_keep: days_to_keep,
          max_retries: max_retries,
          retry_waitsec: retry_waitsec,
          fog_options: fog_options
        )
      end

      def transfer!
        package.filenames.each do |filename|
          src = File.join(Config.tmp_path, filename)
          dest = File.join(remote_path, filename)
          Logger.info "Storing '#{container}/#{dest}'..."
          cloud_io.upload(src, dest)
        end

        package.no_cycle = true if days_to_keep
      end

      # Called by the Cycler.
      # Any error raised will be logged as a warning.
      def remove!(package)
        Logger.info "Removing backup package dated #{package.time}..."

        remote_path = remote_path_for(package)
        objects = cloud_io.objects(remote_path)

        raise Error, "Package at '#{remote_path}' not found" if objects.empty?

        slo_objects, objects = objects.partition(&:slo?)
        cloud_io.delete_slo(slo_objects)
        cloud_io.delete(objects)
      end

      def check_configuration
        required = %w[username api_key container]
        raise Error, <<-EOS if required.map { |name| send(name) }.any?(&:nil?)
          Configuration Error
          #{required.map { |name| "##{name}" }.join(", ")} are all required
        EOS

        raise Error, <<-EOS if segment_size > 0 && segments_container.to_s.empty?
          Configuration Error
          #segments_container is required if #segment_size is > 0
        EOS

        raise Error, <<-EOS if container == segments_container
          Configuration Error
          #container and #segments_container must not be the same container.
        EOS

        raise Error, <<-EOS if segment_size > 5120
          Configuration Error
          #segment_size is too large (max 5120)
        EOS
      end
    end
  end
end
