class CreateSharedExtensionsSchema < ActiveRecord::Migration[6.0]
  def up
    unless schema_exists?(extensions_schema)
      execute_or_log_create_schema_warning("CREATE SCHEMA #{extensions_schema}")
    end

    %w[unaccent pg_trgm].each do |extension|
      if extension_enabled?(extension)
        unless extension_already_in_extensions_schema?(extension)
          execute_or_log_extension_warning("ALTER EXTENSION #{extension} SET SCHEMA #{extensions_schema}")
        end
      else
        execute_or_log_extension_warning("CREATE EXTENSION #{extension} SCHEMA #{extensions_schema}")
      end
    end

    unless schema_exists?(extensions_schema) && public_has_usage_privilege_on_extensions_schema?
      execute_or_log_grant_usage_warning("GRANT usage ON SCHEMA #{extensions_schema} TO public")
    end

    show_full_warning_message if warning_messages.any?
  end

  def down
    %w[unaccent pg_trgm].each do |extension|
      unless extension_already_in_public_schema?(extension)
        execute "ALTER EXTENSION #{extension} SET SCHEMA public;"
      end
    end

    execute "DROP SCHEMA #{extensions_schema};" if schema_exists?(extensions_schema)
  end

  private

    def extensions_schema
      "shared_extensions"
    end

    def extension_already_in_extensions_schema?(extension)
      associated_schema_id_for(extension) == extensions_schema_id
    end

    def extension_already_in_public_schema?(extension)
      associated_schema_id_for(extension) == public_schema_id
    end

    def associated_schema_id_for(extension)
      query_value("SELECT extnamespace FROM pg_extension WHERE extname=#{quote(extension)}")
    end

    def extensions_schema_id
      schema_id_for(extensions_schema)
    end

    def public_schema_id
      schema_id_for("public")
    end

    def schema_id_for(schema)
      query_value("SELECT oid FROM pg_namespace WHERE nspname=#{quote(schema)}")
    end

    def execute_or_log_create_schema_warning(statement)
      if create_permission?
        execute statement
      else
        log_warning(
          "GRANT CREATE ON DATABASE #{query_value("SELECT CURRENT_DATABASE()")} " \
          "TO #{query_value("SELECT CURRENT_USER")}"
        )
        log_warning(statement)
      end
    end

    def execute_or_log_extension_warning(statement)
      if superuser?
        execute statement
      else
        log_warning(statement)
      end
    end

    def execute_or_log_grant_usage_warning(statement)
      if schema_exists?(extensions_schema) && grant_usage_permission?
        execute statement
      else
        log_warning(statement)
      end
    end

    def create_permission?
      query_value("SELECT has_database_privilege(CURRENT_USER, CURRENT_DATABASE(), 'CREATE');")
    end

    def superuser?
      query_value("SELECT usesuper FROM pg_user WHERE usename = CURRENT_USER")
    end

    def grant_usage_permission?
      query_value("SELECT has_schema_privilege(CURRENT_USER, '#{extensions_schema}', 'CREATE');")
    end

    def public_has_usage_privilege_on_extensions_schema?
      query_value("SELECT has_schema_privilege('public', '#{extensions_schema}', 'USAGE');")
    end

    def log_warning(statement)
      warning_messages.push(statement)
    end

    def warning_messages
      @warning_messages ||= []
    end

    def show_full_warning_message
      message = <<~WARNING
        ---------------------- Multitenancy Warning ----------------------
          Multitenancy is a feature that allows managing multiple
          institutions in a completely independent way using just one
          CONSUL DEMOCRACY installation.

          NOTE: If you aren't going to use multitenancy, you can safely
          ignore this warning.

          If you'd like to enable this feature, first run:
            #{warning_messages.join(";\n    ")};
          using a user with enough database privileges.

          Check the CONSUL DEMOCRACY release notes for more information.
        ------------------------------------------------------------------
      WARNING

      puts message
      Rails.logger.warn(message)
    end
end
