require 'active_record'

module Apartment

  module Adapters

    class AbstractAdapter

      #   @constructor
      #   @param {Hash} config Database config
      #   @param {Hash} defaults Some default options
      #
      def initialize(config, defaults = {})
        @config = config
        @defaults = defaults
      end

      #   Create a new database, import schema, seed if appropriate
      #
      #   @param {String} database Database name
      #
      def create(database)
        create_database(database)

        process(database) do
          import_database_schema

          # Seed data if appropriate
          seed_data if Apartment.seed_after_create

          yield if block_given?
        end
      end

      #   Get the current database name
      #
      #   @return {String} current database name
      #
      def current_database
        ActiveRecord::Base.connection.current_database
      end
      alias_method :current, :current_database

      #   Drop the database
      #
      #   @param {String} database Database name
      #
      def drop(database)
        # ActiveRecord::Base.connection.drop_database   note that drop_database will not throw an exception, so manually execute
        ActiveRecord::Base.connection.execute("DROP DATABASE #{environmentify(database)}" )

      rescue ActiveRecord::StatementInvalid
        raise DatabaseNotFound, "The database #{environmentify(database)} cannot be found"
      end

      #   Connect to db, do your biz, switch back to previous db
      #
      #   @param {String?} database Database or schema to connect to
      #
      def process(database = nil)
        current_db = current_database
        switch(database)
        yield if block_given?

      ensure
        switch(current_db) rescue reset
      end

      #   Establish a new connection for each specific excluded model
      #
      def process_excluded_models
        # All other models will shared a connection (at ActiveRecord::Base) and we can modify at will
        Apartment.excluded_models.each do |excluded_model|
          # Note that due to rails reloading, we now take string references to classes rather than
          # actual object references.  This way when we contantize, we always get the proper class reference
          if excluded_model.is_a? Class
            warn "[Deprecation Warning] Passing class references to excluded models is now deprecated, please use a string instead"
            excluded_model = excluded_model.name
          end

          excluded_model.constantize.establish_connection @config
        end
      end

      #   Reset the database connection to the default
      #
      def reset
        ActiveRecord::Base.establish_connection @config
      end

      #   Switch to new connection (or schema if appopriate)
      #
      #   @param {String} database Database name
      #
      def switch(database = nil)
        # Just connect to default db and return
        return reset if database.nil?

        connect_to_new(database)
      end

      #   Load the rails seed file into the db
      #
      def seed_data
        silence_stream(STDOUT){ load_or_abort("#{Rails.root}/db/seeds.rb") } # Don't log the output of seeding the db
      end
      alias_method :seed, :seed_data

    protected

      #   Create the database
      #
      #   @param {String} database Database name
      #
      def create_database(database)
        ActiveRecord::Base.connection.create_database( environmentify(database) )

      rescue ActiveRecord::StatementInvalid
        raise DatabaseExists, "The database #{environmentify(database)} already exists."
      end

      #   Connect to new database
      #
      #   @param {String} database Database name
      #
      def connect_to_new(database)
        ActiveRecord::Base.establish_connection multi_tenantify(database)
        ActiveRecord::Base.connection.active?   # call active? to manually check if this connection is valid

      rescue ActiveRecord::StatementInvalid
        raise DatabaseNotFound, "The database #{environmentify(database)} cannot be found."
      end

      #   Prepend the environment if configured and the environment isn't already there
      #
      #   @param {String} database Database name
      #   @return {String} database name with Rails environment *optionally* prepended
      #
      def environmentify(database)
        Apartment.prepend_environment && !database.include?(Rails.env) ? "#{Rails.env}_#{database}" : database
      end

      #   Import the database schema
      #
      def import_database_schema
        ActiveRecord::Schema.verbose = false    # do not log schema load output.
        load_or_abort("#{Rails.root}/db/schema.rb")
      end

      #   Return a new config that is multi-tenanted
      #
      def multi_tenantify(database)
        @config.clone.tap do |config|
          config[:database] = environmentify(database)
        end
      end

      #   Load a file or abort if it doesn't exists
      #
      def load_or_abort(file)
        if File.exists?(file)
          load(file)
        else
          abort %{#{file} doesn't exist yet}
        end
      end

    end
  end
end
