/* ** ******************************************************************* ** * 
 *                                CONSTANTS                                  * 
 * ** ******************************************************************* ** */
// Switch so you don't excidentally create a whole lot of users
final Boolean INSERT_USER = false;

// Switch to deactivate the created users so we dont expose the wrong users
final Boolean DEACTIVATE_USERS = true;

// Username / Email address post fixes, update these to match your sandbox
final String EMAIL_EXTENSION		= '@example.com.invalid';
final String UNAME_EXTENSION		= '@example.com.demo';
final String SYS_ADMIN_PROFILE_NAME	= 'System Administrator';

// Specify a start url so users can easily be redirected during demos
final String START_URL	= '/lightning/n/Tree_Applications';

// Override any values on the user object
final Map<String,Object> CUSTOM_DATA = new Map<String,Object>{
	'FirstName' => 'Henk 07',
	'LastName'	=> 'de Vries 07'
};


/* ** ******************************************************************* ** * 
 *                                VARIABLES                                  * 
 * ** ******************************************************************* ** */
// User counter so multiple users can be created at the same time using the same method
Integer userCounter = 0;


/* ** ******************************************************************* ** * 
 *                            ANONYMOUS EXECUTION                            * 
 * ** ******************************************************************* ** */
try{
	// Create the demo user
	User u = createDemoUser(SYS_ADMIN_PROFILE_NAME, CUSTOM_DATA);
	
	// Create a random password basedon the username (unique) + current timestamp
	String password = EncodingUtil.convertToHex(Crypto.generateDigest('SHA1', Blob.valueOf(u.username + DateTime.now().getTime())));
	
	// Check if the user needs to be inserted into the database
	if(INSERT_USER){
		// Create the user
		insert u;

		// Set the password
		System.setPassword(u.Id,password);
	}

	// Output an easy login url details
	System.debug(password);

	// Very bad, but good for demos 8-)
	System.debug(URL.getSalesforceBaseUrl().toExternalForm() + '/login.jsp?un=' + u.Username +'&startURL='+START_URL);
	
	// Debug the user info
	System.debug(JSON.serializePretty(u));
	
	// Deactivate the users
	if(DEACTIVATE_USERS){
		deactivateUsers();
	}
	
}catch(Exception e){
	System.debug(e.getMessage());
}


/* ** ******************************************************************* ** * 
 *                             PUBLIC SUPPORT METHODS                        * 
 * ** ******************************************************************* ** */
/**
 * Method for creating a demo user record
 */
public User createDemoUser(String profileName, Map<String,Object> dataMap){

	// Check profile name is not blank
	if(String.isBlank(profileName)){
		throw new StringException('Profile name cannot be blank');
	}

	// Query profile Id
	String profileId = [SELECT Id FROM Profile WHERE Name = :profileName WITH SECURITY_ENFORCED LIMIT 1]?.Id;

	// Check the profile exists else throw an error
	if(profileId == null){
		throw new StringException('Profile does not exist, check the profile name');
	}
	
	// Generate a semi random username for a test user
	String userName = EncodingUtil.convertToHex(Crypto.generateDigest('SHA1', Blob.valueOf(UNAME_EXTENSION + userCounter + DateTime.now().getTime()))).substring(0,25);
		
	// Setup a user, so you can run the test code for a specific profile / permission set
	// In out best practice we use permission sets, so it's best to test these permission sets accordingly with the proper user Profile
	User runAsUser = new User();
	runAsUser.Alias 				= userName.subString(0,8);
	runAsUser.UserName		 		= userName + UNAME_EXTENSION;
	runAsUser.Email			 		= userName + EMAIL_EXTENSION;
	runAsUser.LastName				= userName;
	runAsUser.FederationIdentifier	= userName;
	runAsUser.EmailEncodingKey		= 'UTF-8';
	runAsUser.LanguageLocaleKey		= 'en_US';
	runAsUser.LocaleSidKey			= 'en_US';
	runAsUser.TimeZoneSidKey		= 'Europe/London';
	runAsUser.ProfileId				= profileId;
	
	// Allow the developers to populate additional data to the user record or override any of the default values
	populateSObjectDataFromMap(runAsUser,dataMap);
	
	// Update the user counter so the next one can be created
	userCounter++;
		
	// Return the new user
	return runAsUser;
}

/* ** ******************************************************************* ** * 
 *                 PRIVATE SUPPORT METHODS (CLASS SPECIFIC)                  * 
 * ** ******************************************************************* ** */
/**
 * Method to populate data on an sObject from a data map
 */
private static void populateSObjectDataFromMap(sObject sObj, Map<String,Object> dataMap){
	// Null check
	if(sObj ==  null || dataMap == null){return;}

	// Fetch the fields from the metadata so we can check they exist
	Schema.SObjectType sObjectType = sObj.getSObjectType();
	Map<String, Schema.SObjectField> fieldMap = sObjectType.getDescribe(SObjectDescribeOptions.DEFERRED).fields.getMap();

	// Populate the data from the data map
	for(String fieldName : dataMap.keySet()){

		// Check the field exists on the sObject
		if(!fieldMap.containsKey(fieldName)){
			throw new StringException(String.format('The field "{0}" on sObject "{1}" cannot be found in the metadata', new String[]{fieldName,String.valueOf(sObjectType)}));
		}

		// Populate the field
		sObj.put(fieldName,dataMap.get(fieldName));
	}
}

void deactivateUsers(){
	User[] users = [SELECT Id,Name FROM User WHERE CreatedById = :UserInfo.getUserId() AND Id != :UserInfo.getUserId() AND IsActive=true];
	for(User u : users){
		u.isActive = false;
	}
	update users;
	System.debug(JSON.serializePretty(users));
}