global class OIDCTrustBuilder implements Auth.RegistrationHandler{

    // Function to check if we got mandatories values to create the SF user from the OIDC id_token
    // Can be adjust if more values from scopes are needed 
    global boolean canCreateUser(Auth.UserData data) {
        System.debug('*-*-*-* canCreateUser was called for ' + data);
        Boolean retVal = (data != null 
                && data.attributeMap.get('sub') != null
                && data.attributeMap.get('family_name') != null
                && data.attributeMap.get('given_name') != null);
    
        // id_token subject is the user email address
        System.debug('*-*-*-* data.username=' +data.attributeMap.get('sub'));
        System.debug('*-*-*-* data.lastName=' +data.attributeMap.get('family_name'));
        System.debug('*-*-*-* data.firstName='+data.attributeMap.get('given_name'));
    
        return retVal;
    }
    
    global User createUser(Id portalId, Auth.UserData data){
            
        // data contains OIDC id_token datas
        User u;
        // use OIDC id_token sub as Subject
        String Subject = data.attributeMap.get('sub');
        System.debug('*-*-*-* oidc: ' + data);
        System.debug('*-*-*-* oidc subject: ' + Subject);
        
        // Try to find an existing user first
        List<User> existingUsers = [SELECT UserName FROM User WHERE UserName =: Subject ];
        
        // log matching users nb
        System.debug('*-*-*-* Nb MatchingUsers : ' + existingUsers.size() );
        
        if (existingUsers.size() > 0) {
            // matching user found, return user and SF will call the updateUser function
            System.debug('*-*-*-* 1st matching user : ' + existingUsers[0]);
            u = existingUsers[0];
        } else {
            // No matching user found, trying to create the user in the SF org
            System.debug('*-*-*-* No matching user, try creating one');

            // Check mandatory values
            if(!canCreateUser(data)) {
                // Returning null signals the auth framework we can't create the user
                System.debug('*-*-*-* Error creating user, check mandatory values');
                return null;
                }
                
            // If no user exists, create a new one
            u = new User();
            // OIDC uses given_name, family_name instead of firstName, lastname as the attributes keys with standard scope profile
            String firstName = data.attributeMap.containsKey('given_name') ? data.attributeMap.get('given_name') : 'NoFirstName'; 
            String lastName = data.attributeMap.containsKey('family_name') ? data.attributeMap.get('family_name') : 'NoLastName'; 
            
            // /!\ UserName MUST BE UNIQUE ACROSS ALL SalesForce org, so use a username likely unique like email address
            u.Username = data.attributeMap.containsKey('sub') ? data.attributeMap.get('sub') : 'default@email.com';
            u.Email = data.attributeMap.containsKey('sub') ? data.attributeMap.get('sub') : 'default@email.com';
            
            // 'Chatter Free User' profile useful for test' and demos as free SF licence
            Profile p = [SELECT Id FROM Profile WHERE Name = 'Chatter Free User' LIMIT 1];
            // 'Standard User' profile for production or others, consult SF doc
            // Profile p = [SELECT Id FROM Profile WHERE Name = 'Standard User' LIMIT 1];
            
            u.firstname = firstName ;
            u.lastname = lastName ;
            
            // Use the sub as FederationIdentifier 
            u.FederationIdentifier = data.attributeMap.get('sub');
            // Alias logic : 4 1st firstname digit + 4 1st lastname digit (SF mandatory 8 digits)
            u.Alias = (u.FirstName + u.LastName).length() > 8 ? ((u.firstName).substring(0, 4) + (u.lastName).substring(0, 4)) : (u.firstName + u.lastName);
            u.ProfileId = p.Id; // Profile Id, see above

            // Hard coded values, can be adjusted from OIDC scopes
            u.TimeZoneSidKey = 'GMT';
            u.LocaleSidKey = 'fr_FR_EURO';
            u.EmailEncodingKey = 'UTF-8';
            u.LanguageLocaleKey = 'fr';

            // If working with Communities or Portals, you might also need to set additional fields like AccountId, ContactId, etc.
            
            insert u;
        }
        
        return u;
    }

    // Called by the SF Auth Framework
    global void updateUser(Id userId, Id portalId, Auth.UserData data){
        User u = [SELECT Id, Email, FirstName, LastName, ProfileId, alias, Username, FederationIdentifier FROM User WHERE Email =: data.attributeMap.get('sub') LIMIT 1];

        System.debug('*-*-*-* Selected existing user: ' + u);
        
        // 'Chatter Free User' profile useful for test' and demos as free SF licence
        // 'Standard User' profile for production
        // Profile p = [SELECT Id FROM Profile WHERE Name = 'Standard User' LIMIT 1];
        // Profile p = [SELECT Id FROM Profile WHERE Name = 'Chatter Free User' LIMIT 1];

        System.debug('*-*-*-* data: ' + data);
        System.debug('*-*-*-* subject   from idtoken: ' + data.attributeMap.get('sub'));
        System.debug('*-*-*-* firstname from idtoken: ' + data.attributeMap.get('firstname'));
        System.debug('*-*-*-* lastname  from idtoken: ' + data.attributeMap.get('lastname'));
        
        // Update the user's informations based on the incoming data
        // Can be adjusted from OIDC scopes
        // use idtoken sub as email adress
        u.UserName  = data.attributeMap.containsKey('sub')       ? data.attributeMap.get('sub')       : u.Email;
        u.FirstName = data.attributeMap.containsKey('firstname') ? data.attributeMap.get('firstname') : u.FirstName;
        u.LastName  = data.attributeMap.containsKey('lastname')  ? data.attributeMap.get('lastname')  : u.LastName;
        
        // u.ProfileId = p.Id;
        // Update other fields as necessary
        System.debug('*-*-*-* Updated user: ' + u);
        
        update u;
    }
}