/***********************************************************************************************************
 * Name         : UserTriggerHelper
 * Purpose      : Helper methods for UserTrigger
 **********************************************************************************************************/
public class UserTriggerHelper {
    private static final String KEY_CREATED_DATE = 'CREATED_DATE';
    private static final String KEY_NAME = 'NAME';
    private static final String KEY_USERNAME = 'USERNAME';
    private static final String KEY_PROFILE = 'PROFILE';
    private static final String KEY_CREATED_BY = 'CREATED_BY';
    private static final String KEY_LAST_MODIFIED_BY = 'LAST_MODIFIED_BY';
    private static final String KEY_ACTION = 'ACTION';
    private static final String KEY_EMAIL = 'EMAIL';

    @TestVisible
    private static final String ACTION_NEW = 'New User';
    @TestVisible
    private static final String ACTION_UPDATED = 'Updated User';
    @TestVisible
    private static final String ACTION_DEACTIVATED = 'Deactivated User';
    @TestVisible
    private static final String ACTION_REACTIVATED = 'Reactivated User';

    @TestVisible
    private static Map<Id, String> userActions = new Map<Id, String>();

    /**
     * Serializes parameters and invokes the @future method to send user management alert emails for User trigger events.
     *
     * @param newUsers List of new User records (Trigger.new)
     * @param oldMap Map of old User records (Trigger.oldMap). If null, this is an insert context; if not null, update context.
     */
    public static void sendUserManagementAlerts(List<User> newUsers, Map<Id, User> oldMap) {
        if (!Test.isRunningTest()) {
            List<User> newUsersToProcess = new List<User>();
            Map<Id, User> oldMapToProcess = new Map<Id, User>();
            for (User newUser : newUsers) {
                User oldUser = (oldMap != null) ? oldMap.get(newUser.Id) : null;
                if (
                    oldUser == null ||
                    TriggerUtils.fieldHasChanged(oldUser, newUser, 'ProfileId') ||
                    TriggerUtils.fieldHasChanged(oldUser, newUser, 'Username') ||
                    TriggerUtils.fieldHasChanged(oldUser, newUser, 'IsActive') ||
                    TriggerUtils.fieldHasChanged(oldUser, newUser, 'Email') ||
                    TriggerUtils.fieldHasChanged(oldUser, newUser, 'FirstName') ||
                    TriggerUtils.fieldHasChanged(oldUser, newUser, 'LastName')
                ) {
                    newUsersToProcess.add(newUser);
                    if (oldUser != null) {
                        oldMapToProcess.put(newUser.Id, oldUser);
                    }
                }
            }

            if (!newUsersToProcess.isEmpty()) {
                String newUsersJson = JSON.serialize(newUsersToProcess);
                String oldMapJson = (oldMap == null) ? null : JSON.serialize(oldMapToProcess);
                sendUserManagementAlertsFuture(newUsersJson, oldMapJson);
            }
        }
    }

    /**
     * Sends user management alert emails for User trigger events.
     *
     * @param newUsers List of new User records (Trigger.new)
     * @param oldMap Map of old User records (Trigger.oldMap). If null, this is an insert context; if not null, update context.
     */
    @future
    public static void sendUserManagementAlertsFuture(String newUsersJson, String oldMapJson) {
        try {
            EmailTemplate template = [
                SELECT Id, HtmlValue, Subject
                FROM EmailTemplate
                WHERE DeveloperName = 'UserManagementAlert'
                LIMIT 1
            ];

            String recipientEmail = ConfigUtils.getConfigString(ConfigUtils.USER_MGMT_ALERT_RECIPIENT);
            Id recipientId = [SELECT Id FROM User WHERE Email = :recipientEmail LIMIT 1].Id;

            List<User> newUsers = (List<User>) JSON.deserialize(newUsersJson, List<User>.class);
            Map<Id, User> oldMap = (oldMapJson == null ||
                oldMapJson == '')
                ? null
                : (Map<Id, User>) JSON.deserialize(oldMapJson, Map<Id, User>.class);
            List<Messaging.SingleEmailMessage> emails = new List<Messaging.SingleEmailMessage>();
            Set<Id> profileIds = new Set<Id>();
            Set<Id> userIds = new Set<Id>();
            List<User> usersToSend = new List<User>();

            Map<Id, Map<String, String>> changeDetails = new Map<Id, Map<String, String>>();
            Map<Id, User> oldUsersMap = new Map<Id, User>();
            for (User newUser : newUsers) {
                profileIds.add(newUser.ProfileId);
                userIds.add(newUser.CreatedById);
                userIds.add(newUser.LastModifiedById);
                if (oldMap != null && oldMap.containsKey(newUser.Id)) {
                    User oldUser = oldMap.get(newUser.Id);
                    profileIds.add(oldUser.ProfileId);
                    oldUsersMap.put(newUser.Id, oldUser);
                }
            }

            Map<Id, Profile> profileMap = new Map<Id, Profile>();
            Map<Id, User> userMap = new Map<Id, User>();
            if (!profileIds.isEmpty()) {
                profileMap = new Map<Id, Profile>([SELECT Id, Name FROM Profile WHERE Id IN :profileIds]);
            }
            if (!userIds.isEmpty()) {
                userMap = new Map<Id, User>([SELECT Id, Name FROM User WHERE Id IN :userIds]);
            }

            for (User newUser : newUsers) {
                Boolean shouldSend = false;
                String action = ACTION_UPDATED;
                Map<String, String> details = new Map<String, String>();
                if (oldMap == null) {
                    action = ACTION_NEW;
                    shouldSend = true;
                    details.put(KEY_PROFILE, profileMap.get(newUser.ProfileId).Name);
                    details.put(
                        KEY_NAME,
                        (newUser.FirstName != null ? newUser.FirstName + ' ' : '') +
                        (newUser.LastName != null ? newUser.LastName : '')
                    );
                    details.put(KEY_USERNAME, newUser.Username);
                    details.put(KEY_EMAIL, newUser.Email);
                } else {
                    User oldUser = oldUsersMap.get(newUser.Id);
                    if (oldUser.ProfileId != newUser.ProfileId) {
                        shouldSend = true;
                        String oldProfile = profileMap.containsKey(oldUser.ProfileId)
                            ? profileMap.get(oldUser.ProfileId).Name
                            : String.valueOf(oldUser.ProfileId);
                        String newProfile = profileMap.containsKey(newUser.ProfileId)
                            ? profileMap.get(newUser.ProfileId).Name
                            : String.valueOf(newUser.ProfileId);
                        details.put(KEY_PROFILE, oldProfile + ' ➜ <b>' + newProfile + '</b>');
                    } else {
                        details.put(
                            KEY_PROFILE,
                            profileMap.containsKey(newUser.ProfileId)
                                ? profileMap.get(newUser.ProfileId).Name
                                : String.valueOf(newUser.ProfileId)
                        );
                    }
                    if (oldUser.Username != newUser.Username) {
                        shouldSend = true;
                        details.put(KEY_USERNAME, oldUser.Username + ' ➜ <b>' + newUser.Username + '</b>');
                    } else {
                        details.put(KEY_USERNAME, newUser.Username);
                    }
                    String oldFullName =
                        (oldUser.FirstName != null ? oldUser.FirstName + ' ' : '') +
                        (oldUser.LastName != null ? oldUser.LastName : '');
                    String newFullName =
                        (newUser.FirstName != null ? newUser.FirstName + ' ' : '') +
                        (newUser.LastName != null ? newUser.LastName : '');
                    if (oldFullName.trim() != newFullName.trim()) {
                        shouldSend = true;
                        details.put(KEY_NAME, oldFullName.trim() + ' ➜ <b>' + newFullName.trim() + '</b>');
                    } else {
                        details.put(KEY_NAME, newFullName.trim());
                    }
                    if (oldUser.IsActive != newUser.IsActive) {
                        shouldSend = true;
                        if (oldUser.IsActive && !newUser.IsActive) {
                            action = ACTION_DEACTIVATED;
                        } else if (!oldUser.IsActive && newUser.IsActive) {
                            action = ACTION_REACTIVATED;
                        }
                    }
                    if (oldUser.Email != newUser.Email) {
                        shouldSend = true;
                        details.put(KEY_EMAIL, oldUser.Email + ' ➜ <b>' + newUser.Email + '</b>');
                    } else {
                        details.put(KEY_EMAIL, newUser.Email);
                    }
                }
                if (shouldSend) {
                    usersToSend.add(newUser);
                    userActions.put(newUser.Id, action);
                    changeDetails.put(newUser.Id, details);
                }
            }

            if (!usersToSend.isEmpty()) {
                for (User newUser : usersToSend) {
                    String action = userActions.get(newUser.Id);
                    Map<String, String> replacements = new Map<String, String>{
                        KEY_CREATED_DATE => String.valueOf(newUser.CreatedDate),
                        KEY_NAME => changeDetails.get(newUser.Id).get(KEY_NAME),
                        KEY_USERNAME => changeDetails.get(newUser.Id).get(KEY_USERNAME),
                        KEY_EMAIL => changeDetails.get(newUser.Id).get(KEY_EMAIL),
                        KEY_PROFILE => changeDetails.get(newUser.Id).get(KEY_PROFILE),
                        KEY_CREATED_BY => userMap.get(newUser.CreatedById).Name,
                        KEY_LAST_MODIFIED_BY => userMap.get(newUser.LastModifiedById).Name,
                        KEY_ACTION => action
                    };
                    String htmlBody = applyTemplate(template, replacements);
                    String subject = template.Subject.replace('{{ACTION}}', action);
                    emails.add(createEmail(subject, htmlBody, recipientId));
                }
                if (!emails.isEmpty()) {
                    Messaging.sendEmail(emails);
                }
            }
        } catch (Exception ex) {
            ErrorLogger.logError(ex);
        }
    }

    /**
     * Applies the given replacements to the email template HTML.
     *
     * @param replacements Map of placeholder keys to replacement values
     * @return The HTML string with placeholders replaced
     */
    private static String applyTemplate(EmailTemplate template, Map<String, String> replacements) {
        String result = template.HtmlValue;
        for (String key : replacements.keySet()) {
            result = result.replace('{{' + key + '}}', replacements.get(key) ?? '');
        }
        return result;
    }

    /**
     * Creates a SingleEmailMessage for the given subject and body.
     *
     * @param subject The email subject
     * @param body The email HTML body
     * @return The constructed Messaging.SingleEmailMessage
     */
    private static Messaging.SingleEmailMessage createEmail(String subject, String body, Id recipientId) {
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        mail.setTargetObjectId(recipientId);
        mail.setSaveAsActivity(false);
        mail.setSubject(subject);
        mail.setHtmlBody(body);
        return mail;
    }
}