# frozen_string_literal: true

#-------------------------------------------------------------------------
# # Copyright (c) Microsoft and contributors. All rights reserved.
#
# The MIT License(MIT)

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#--------------------------------------------------------------------------

module Azure::Storage::Common
  # The Azure::Storage::Common::Configurable module provides basic configuration for Azure storage activities.
  module Configurable
    # @!attribute [w] storage_access_key
    #   @return [String] Azure Storage access key.
    # @!attribute storage_account_name
    #   @return [String] Azure Storage account name.
    # @!attribute storage_connection_string
    #   @return [String] Azure Storage connection string.
    # @!attribute storage_blob_host
    #   @return [String] Set the host for the Blob service. Only set this if you want
    #     something custom (like, for example, to point this to a LocalStorage
    #     emulator). This should be the complete host, including http:// at the
    #     start. When using the emulator, make sure to include your account name at
    #     the end.
    # @!attribute storage_table_host
    #   @return [String] Set the host for the Table service. Only set this if you want
    #     something custom (like, for example, to point this to a LocalStorage
    #     emulator). This should be the complete host, including http:// at the
    #     start. When using the emulator, make sure to include your account name at
    #     the end.
    # @!attribute storage_queue_host
    #   @return [String] Set the host for the Queue service. Only set this if you want
    #     something custom (like, for example, to point this to a LocalStorage
    #     emulator). This should be the complete host, including http:// at the
    #     start. When using the emulator, make sure to include your account name at
    #     the end.

    attr_accessor :storage_access_key,
                  :storage_account_name,
                  :storage_connection_string,
                  :storage_sas_token

    attr_writer :storage_table_host,
                :storage_blob_host,
                :storage_queue_host,
                :storage_file_host,
                :storage_table_host_secondary,
                :storage_blob_host_secondary,
                :storage_queue_host_secondary,
                :storage_file_host_secondary

    attr_reader :signer

    class << self
      # List of configurable keys for {Azure::Client}
      # @return [Array] of option keys
      def keys
        @keys ||= [
          :storage_access_key,
          :storage_account_name,
          :storage_connection_string,
          :storage_sas_token,
          :storage_table_host,
          :storage_blob_host,
          :storage_queue_host,
          :storage_file_host,
          :signer
        ]
      end
    end

    # Set configuration options using a block
    def configure
      yield self
    end

    def config
      self
    end

    # Reset configuration options to default values
    def reset_config!(options = {})
      Azure::Storage::Common::Configurable.keys.each do |key|
        value =
          if self == Azure::Storage::Common
            Azure::Storage::Common::Default.options[key]
          else
            self.send(key)
          end
        instance_variable_set(:"@#{key}", options.fetch(key, value))

        # Set the secondary endpoint if the primary one is given
        if key.to_s.include? "host"
          instance_variable_set(:"@#{key}_secondary", secondary_endpoint(options.fetch(key, value)))
        end
      end
      self.send(:reset_agents!) if self.respond_to?(:reset_agents!)
      setup_signer_for_service(options[:api_version])
      self
    end

    alias setup reset_config!

    # Storage queue host
    # @return [String]
    def storage_queue_host(isSecondary = false)
      if isSecondary
        @storage_queue_host_secondary || default_host(:queue, true)
      else
        @storage_queue_host || default_host(:queue, false)
      end
    end

    # Storage blob host
    # @return [String]
    def storage_blob_host(isSecondary = false)
      if isSecondary
        @storage_blob_host_secondary || default_host(:blob, true)
      else
        @storage_blob_host || default_host(:blob, false)
      end
    end

    # Storage table host
    # @return [String]
    def storage_table_host(isSecondary = false)
      if isSecondary
        @storage_table_host_secondary || default_host(:table, true)
      else
        @storage_table_host || default_host(:table, false)
      end
    end

    # Storage file host
    # @return [String]
    def storage_file_host(isSecondary = false)
      if isSecondary
        @storage_file_host_secondary || default_host(:file, true)
      else
        @storage_file_host || default_host(:file, false)
      end
    end

    private

      def default_host(service, isSecondary = false)
        "https://#{storage_account_name}#{isSecondary ? "-secondary" : ""}.#{service}.core.windows.net" if storage_account_name
      end

      def setup_options
        opts = {}
        Azure::Storage::Common::Configurable.keys.map do |key|
          opts[key] = self.send(key) if self.send(key)
        end
        opts
      end

      def account_name_from_endpoint(endpoint)
        return nil if endpoint.nil?
        uri = URI::parse endpoint
        fields = uri.host.split "."
        fields[0]
      end

      def secondary_endpoint(primary_endpoint)
        return nil if primary_endpoint.nil?
        account_name = account_name_from_endpoint primary_endpoint
        primary_endpoint.sub account_name, account_name + "-secondary"
      end

      def determine_account_name
        if instance_variable_get(:@storage_account_name).nil?
          hosts = [@storage_blob_host, @storage_table_host, @storage_queue_host, @storage_file_host]
          account_name = nil;
          hosts.each do |host|
            parsed = account_name_from_endpoint host
            if account_name.nil?
              account_name = parsed
            elsif !account_name.nil? && !parsed.nil? && (account_name <=> parsed) != (0)
              raise InvalidOptionsError, "Ambiguous account name in service hosts."
            end
          end
          raise InvalidOptionsError, "Cannot identify account name." if account_name.nil?
          @storage_account_name = account_name
        end
      end

      def setup_signer_for_service(api_ver)
        if @storage_sas_token
          determine_account_name
          @signer = Azure::Storage::Common::Core::Auth::SharedAccessSignatureSigner.new api_ver, @storage_account_name, @storage_sas_token
        end
      end
  end
end
