require "helpful_configuration"

require "rails_connector/cms_base_model"
require "rails_connector/blob"

module RailsConnector

  # {RailsConnector::Configuration} is used to en/disable and initialize addons.
  # @api public
  class Configuration
    # @api public
    DEFAULT_MODE = :live

    #
    # Default fields which the {DefaultUserController} will store in the session.
    #
    # @api public
    DEFAULT_STORE_USER_ATTRS_IN_SESSION = [:login, :first_name, :last_name, :email, :id]

    #
    # Default path of a CA certification file in PEM format.
    #
    # @api public
    DEFAULT_CA_FILE = File.expand_path('../../../config/ca-bundle.crt', __FILE__)

    @features = {}

    class << self
      # Automatically generate editmarkers when rendering liquid templates in editor mode.
      # @api public
      attr_accessor :auto_liquid_editmarkers

      # there are three available modes for the rails connector:
      # <tt>live</tt> (show released contents only),
      # <tt>preview</tt> (show edited contents)
      # <tt>editor</tt> (show edited contents and editor interface (e.g. edit markers))
      # Default mode is <tt>live</tt>.
      # @api public
      attr_accessor :mode

      # default options for {SearchRequest}
      # @api public
      attr_writer :search_options

      # Cache also editable workspaces.
      # Editable workspaces will be cached until the cache is cleared manually.
      # Should never be used in a production environment.
      # @api public
      attr_accessor :cache_editable_workspaces

      # Include ReCaptcha tags in user registration form and validate the captcha
      # when creating a new user registration (default: true).
      # @api public
      attr_accessor :use_recaptcha_on_user_registration

      # define a non-default blob cache dir.
      # +fiona connector+ only. has no effect when used with the +cloud connector+.
      # @api public
      attr_accessor :blob_cache_dir

      # Configuration for Content Read Service API.
      # @api public
      attr_reader :content_service

      # Determine if current visitor is permitted to edit content.
      attr_accessor :editing_auth_callback

      # Configure a callback to be invoked when the rails connector determines,
      # if current visitor is permitted to edit content.
      # Default is <code>false</code>.
      #
      # Example Usage:
      #     RailsConnector::Configuation.editing_auth do |env|
      #       request = Rack::Request.new(env)
      #       # return truey if current visitor is permitted to edit content, falsy otherwise
      #     end
      # @api public
      def editing_auth(&block)
        if block.respond_to?(:arity) && block.arity == 1
          self.editing_auth_callback = block
        else
          raise ArgumentError, 'editing_auth is not callable with arity one!'
        end
      end

      # Gets path of a CA certification file in PEM format.
      # @api public
      attr_reader :ca_file

      # Sets path of a CA certification file in PEM format.
      # The file can contain several CA certificates.
      # Certifications will be used for endpoint peer verification of various Infopark services
      # e.g. Content Read Service.
      # @api public
      def ca_file=(path)
        File.read(path) if path # Try to read the given file and fail if it doesn't exist or is not readable.
        @ca_file = path
      end

      # default options for {SearchRequest}
      # @api public
      def search_options
        @search_options || local_config_file["search"].symbolize_keys
      end

      # @api public
      def mode=(new_mode)
        new_mode = new_mode.to_sym
        raise ArgumentError, "Unknown Mode #{new_mode}" unless [:editor, :preview, :live].include?(new_mode)
        @mode = new_mode
      end

      # there are three available modes for the rails connector:
      # <tt>live</tt> (show released contents only),
      # <tt>preview</tt> (show edited contents)
      # <tt>editor</tt> (show edited contents and editor interface (e.g. edit markers))
      # Default mode is <tt>live</tt>.
      # @api public
      def mode
        @mode || DEFAULT_MODE
      end

      #
      # Sets the array of fields which the +DefaultUserController+ will store in the session.
      # Defaults to {DEFAULT_STORE_USER_ATTRS_IN_SESSION}.
      #
      # @api public
      attr_writer :store_user_attrs_in_session

      #
      # Returns fields which the {DefaultUserController} stores in the session.
      # Defaults to {DEFAULT_STORE_USER_ATTRS_IN_SESSION}.
      #
      # @api public
      def store_user_attrs_in_session
        @store_user_attrs_in_session || DEFAULT_STORE_USER_ATTRS_IN_SESSION
      end

      def editor_interface_enabled?
        mode == :editor
      end

      def use_edited_content?
        mode == :preview || mode == :editor
      end

      def register_features(*features)
        features.each do |f|
          @features.merge!(f.to_sym => false)
        end
      end
      alias :register_feature :register_features

      def registered_features
        @features.keys
      end

      # Rails Connector Addons can be enabled as follows (in <tt>config/rails_connector.rb</tt>):
      #
      #   RailsConnector::Configuration.enable(
      #     :search,
      #     :time_machine,
      #     :crm
      #   )
      #
      # @api public
      def enable(*features)
        features.each do |f|
          assert_feature_is_known(f)
          @features[f.to_sym] = true
        end
        initialize_addon_mixins
      end

      # Returns true if +feature+ is enabled, else false.
      def enabled?(feature)
        assert_feature_is_known(feature)
        @features[feature.to_sym] == true
      end

      # Configures your CMS instance name.
      #
      # Example call as to be used in <tt>rails_connector.rb</tt>:
      #   RailsConnector::Configuration.instance_name = 'internet'
      # @api public
      def instance_name=(name)
        RailsConnector::CmsBaseModel.instance_name = name
      end

      def after_initialize
        enable_authentication
        # Hier muss explizit der Namespace verwendet werden, da diese Methode von Rails
        # während der Initialisierung aufgerufen wird.
        ::RailsConnector::BasicObj.configure_for_content(
          ::RailsConnector::Configuration.use_edited_content? ? :edited : :released
        )
      end

      def to_prepare
        unless Rails.configuration.cache_classes
          after_initialize
          NamedLink.reset_cache
          BasicObj.reset_type_cache
          initialize_addon_mixins
        end
      end

      def configure_cms_database
        if RailsConnector::CmsBaseModel.superclass.to_s == "ActiveRecord::Base"
          CmsBaseModel.configure_database("cms")
        else
          if local_config_file.configured? 'content_service'
            @content_service = local_config_file['content_service']
            Blob.configure(:type => 'service')
          else
            CmsBaseModel.configure_database(local_config_file["cms_database"])
            blob_storage_config = local_config_file["cms_blob_storage"]
            Blob.configure(blob_storage_config)

            dict_storage_config =
                if blob_storage_config["type"] == "s3"
                  cms_dict_storage = local_config_file.with_default({}, "cms_dict_storage")
                  blob_storage_config.merge(cms_dict_storage)
                else
                  local_config_file["cms_dict_storage"]
                end
            DictStorage.configure(dict_storage_config)
          end
          RailsConnector::CmsRestApi.credentials = local_config_file["cms_api"].symbolize_keys
        end
      end

      attr_accessor :choose_homepage_callback

      # Configure a callback to be invoked when the rails connector delivers the homepage.
      # The given callback will receive the rack env
      # and must return an Obj to be used as the homepage.
      # If no callback is configured, Obj.homepage will be used as the default.
      # @api public
      def choose_homepage(&block)
        self.choose_homepage_callback = block
      end

      def cms_routes(*args)
        raise <<-EOS.gsub(/\s+/, ' ')
        Calling RailsConnector::Configuration.cms_routes is not needed anymore.
        Please remove it from config/routes.rb
        EOS
      end

      def use_x_sendfile=(value)
        raise 'Configuration.use_x_sendfile is now available as Rails configuration:'\
              ' config.action_dispatch.x_sendfile_header = "X-Sendfile"'
      end

      def initialize_addon_mixins
        ::ApplicationController.__send__(:helper, :cms)
        if enabled?(:search)
          require "rails_connector/ses"
          RailsConnector::SES.enable
        end
        RailsConnector::Crm.enable if enabled?(:crm)
        RailsConnector::BasicObj.__send__(:include, RailsConnector::Syndicateable) if enabled?(:rss)
        RailsConnector::BasicObj.__send__(:include, RailsConnector::Commentable) if enabled?(:comments)
        RailsConnector::BasicObj.__send__(:include, RailsConnector::Rateable) if enabled?(:ratings)
      end

      def local_config_file
        @local_config_file ||= read_local_config_file
      end

      def local_config_file_name
        (Rails.root + "config/rails_connector.yml").expand_path
      end

      # for test purposes only
      def reset_local_config_file_cache
        @local_config_file = nil
      end

      protected

      def read_local_config_file
        contents = YAML.load_file(local_config_file_name) if File.exists?(local_config_file_name)
        contents ||= {}

        config = HelpfulConfiguration.new(
          contents,
          local_config_file_name
        )
        config.explain(
          "cms_database",
          "a hash of options, including type, that specify the database configuration"
        )
        config.explain(
          "cms_blob_storage",
          "a hash of options, including type, that specify the cms blob storage configuration"
        )
        config.explain(
          "cms_dict_storage",
          "a hash of options, including type, that specify the cms dict storage configuration"
        )
        config.explain(
          "search",
          "a hash of options that specify access to the search server"
        )
        config.explain(
          "cms_api",
          "a hash of options that specify access to the cms api"
        )
        config.explain 'content_service', 'a hash of options that specify access to content service'
        config
      end

      def assert_feature_is_known(f)
        raise ArgumentError, "unknown feature: #{f.inspect}" unless @features.keys.include?(f.to_sym)
      end

      def enable_authentication
        # Wenn das OMC-Features an ist, dann braucht man keine Standardimplementierung
        # für die Authentifizierung.
        unless enabled?(:crm)
          ::ApplicationController.__send__(:include, RailsConnector::Authenticable)
        end
      end
    end

    # defaults
    self.auto_liquid_editmarkers = true
    self.cache_editable_workspaces = false
    self.use_recaptcha_on_user_registration = true
    self.ca_file = DEFAULT_CA_FILE
    self.editing_auth{ |env| false }

    register_features(
      :search, :time_machine, :rss, :comments, :ratings,
      :crm, :seo_sitemap, :google_analytics
    )
  end

  class ConfigurationError < StandardError
  end
end