require_relative './url/arcgis'
require_relative './url/dropbox'
require_relative './url/box'
require_relative './url/gdrive'
require_relative './url/bigquery'
require_relative './url/instagram_oauth'
require_relative './url/mailchimp'
require_relative './url/public_url'
require_relative 'search/twitter'

module CartoDB
  module Datasources
      class DatasourcesFactory
        NAME = 'DatasourcesFactory'.freeze

        # in seconds
        HTTP_CONNECT_TIMEOUT = 60
        DEFAULT_HTTP_REQUEST_TIMEOUT = 600

        # Retrieve a datasource instance
        # @param datasource_name string
        # @param user ::User
        # @param additional_config Hash
        # {
        #   :redis_storage => Redis|nil
        #   :ogr2ogr_instance => Ogr2ogr|nil
        # }
        # @return mixed
        # @throws MissingConfigurationError
        def self.get_datasource(datasource_name, user, additional_config = {})
          # FIXME: does this work with Carto::User user? if not convert it to ::User
          if additional_config[:http_timeout].nil?
            additional_config[:http_timeout] = DEFAULT_HTTP_REQUEST_TIMEOUT
          end
          if additional_config[:http_connect_timeout].nil?
            additional_config[:http_connect_timeout] = HTTP_CONNECT_TIMEOUT
          end

          case datasource_name
            when Url::Dropbox::DATASOURCE_NAME
              Url::Dropbox.get_new(DatasourcesFactory.config_for(datasource_name, user), user)
            when Url::Box::DATASOURCE_NAME
              Url::Box.get_new(DatasourcesFactory.config_for(datasource_name, user), user)
            when Url::GDrive::DATASOURCE_NAME
              Url::GDrive.get_new(DatasourcesFactory.config_for(datasource_name, user), user)
            when Url::BigQuery::DATASOURCE_NAME
              Url::BigQuery.get_new(DatasourcesFactory.config_for(datasource_name, user), user)
            when Url::InstagramOAuth::DATASOURCE_NAME
              Url::InstagramOAuth.get_new(DatasourcesFactory.config_for(datasource_name, user), user)
            when Url::PublicUrl::DATASOURCE_NAME
              Url::PublicUrl.get_new(additional_config)
            when Url::ArcGIS::DATASOURCE_NAME
              Url::ArcGIS.get_new(user)
            when Url::MailChimp::DATASOURCE_NAME
              Url::MailChimp.get_new(DatasourcesFactory.config_for(datasource_name, user).merge(additional_config), user)
            when Search::Twitter::DATASOURCE_NAME
              Search::Twitter.get_new(DatasourcesFactory.config_for(datasource_name, user), user,
                                      additional_config[:redis_storage], additional_config[:user_defined_limits])
            when nil
              nil
            else
              raise MissingConfigurationError.new("unrecognized datasource #{datasource_name}", NAME)
          end
        end

        # Gets all available oauth datasources
        def self.get_all_oauth_datasources
          [
            Url::Dropbox::DATASOURCE_NAME,
            Url::Box::DATASOURCE_NAME,
            Url::GDrive::DATASOURCE_NAME,
            Url::BigQuery::DATASOURCE_NAME,
            # Url::InstagramOAuth::DATASOURCE_NAME,
            Url::MailChimp::DATASOURCE_NAME
          ]
        end

        # Gets the config of a certain datasource
        # @param datasource_name string
        # @param user ::User
        # @return string
        # @throws MissingConfigurationError
        def self.config_for(datasource_name, user)
          config, datasource_supports_custom_config = get_config(datasource_name)

          if datasource_supports_custom_config
            key = customized_config_key(config, datasource_name, user)

            if key.nil?
              config[datasource_name][:standard.to_s]
            else
              # This code assumes config is ok
              name_config_map = config[datasource_name]['entity_to_config_map'].select { |u| !u[key].nil? }.first
              config[datasource_name][:customized.to_s][name_config_map[key]]
            end
          else
            config.fetch(datasource_name)
          end
        end

        def self.customized_config?(datasource_name, user)
          config, datasource_supports_custom_config = get_config(datasource_name)
          datasource_supports_custom_config && customized_config_key(config, datasource_name, user).present?
        end

        # Allows to set a custom config (useful for testing)
        # @param custom_config string
        def self.set_config(custom_config)
          @forced_config = custom_config
        end

        def self.get_config(datasource_name)
          config_source = @forced_config ? @forced_config : Cartodb.config

          datasource_supports_custom_config = false

          case datasource_name
          when Url::Dropbox::DATASOURCE_NAME, Url::Box::DATASOURCE_NAME, Url::GDrive::DATASOURCE_NAME, Url::InstagramOAuth::DATASOURCE_NAME,
               Url::MailChimp::DATASOURCE_NAME, Url::BigQuery::DATASOURCE_NAME
            config = (config_source[:oauth] rescue nil)
            config ||= (config_source[:oauth.to_s] rescue nil)
          when Search::Twitter::DATASOURCE_NAME
            config = (config_source[:datasource_search] rescue nil)
            config ||= (config_source[:datasource_search.to_s] rescue nil)
            datasource_supports_custom_config = true
          else
            config = nil
          end

          if config.nil? || config.empty?
            raise MissingConfigurationError.new("missing configuration for datasource #{datasource_name}", NAME)
          end

          [config, datasource_supports_custom_config]
        end
        private_class_method :get_config

        def self.customized_config_key(config, datasource_name, user)
          custom_config_orgs = config[datasource_name].fetch(:customized_orgs_list.to_s, [])
          custom_config_users = config[datasource_name][:customized_user_list.to_s]

          if user.organization_user? && custom_config_orgs.include?(user.organization.name)
            user.organization.name
          elsif custom_config_users.include?(user.username)
            user.username
          end
        end
        private_class_method :customized_config_key
      end
  end
end

