/***********************************************************************************************************
 * Name         : AWAL_EmailAlertsHandler
 * Purpose      : Class to handle AWAL Email Alerts
 **********************************************************************************************************/
public with sharing class AWAL_EmailAlertsHandler {
    private static EmailTemplate awalEmailTemplate;

    @TestVisible
    private static List<Messaging.SingleEmailMessage> emailsToSend = new List<Messaging.SingleEmailMessage>();

    @TestVisible
    private enum Stages {
        PASSED_TO_FINANCE,
        PASSED_TO_FINANCE_APPROVER,
        FINANCE_ANALYSIS_COMPLETE,
        PASSED_TO_APPROVER,
        APPROVED,
        PASSED_TO_BA,
        CLOSED_LOST
    }

    @TestVisible
    private static List<Schema.FieldSetMember> fieldSetMembers = SObjectType.Opportunity.FieldSets.AWAL_Stage_Notifications.getFields();
    private static Map<Stages, List<Schema.FieldSetMember>> fieldSetMembersMap = new Map<Stages, List<Schema.FieldSetMember>>{
        Stages.CLOSED_LOST => new List<Schema.FieldSetMember>{ fieldSetMembers[0] }
    };

    public static final String EMAIL_TOKEN_MESSAGE = '{{MESSAGE}}';
    public static final String EMAIL_TOKEN_FIELDS = '{{FIELDS}}';
    public static final String EMAIL_TOKEN_URL = '{{URL}}';
    public static final String EMAIL_TOKEN_OPPORTUNITY_NAME = '{{OPPORTUNITY_NAME}}';
    public static final String EMAIL_TOKEN_APPROVERS_INITIALS = '{{APPROVERS_INITIALS}}';
    public static final List<String> RECIPIENTS_TO_REMOVE_FOR_PRIVATE_OPPS = ConfigUtils.getConfigMultipleStrings(
        ConfigUtils.AWAL_ALERT_RECIPIENTS_TO_REMOVE
    );

    /**
     * @description Method just to test the behavior without having to move the Opportunity
     *  between multiple stages
     * @param Id    Opp Id
     * @param String    Stage the opp is moving to
     */
    public static void run(Id oppId, String stage) {
        List<String> fieldApiNameList = new List<String>();
        for (Schema.FieldSetMember fieldSetMember : fieldSetMembers) {
            fieldApiNameList.add(fieldSetMember.getFieldPath());
        }

        String query =
            'SELECT StageName, MasterApproverId__c, CurrencyISOCode, OtherDealMakers__c, IsPrivate, Is_Amendment__c, ' +
            String.join(fieldApiNameList, ',') +
            ' FROM Opportunity WHERE Id = :oppId';
        Opportunity opp = Database.query(query);
        Opportunity oldOpp = opp.clone();
        opp.StageName = stage;
        sendStageChangesNotifications(new List<Opportunity>{ opp }, new Map<Id, Opportunity>{ opp.Id => oldOpp });
    }

    /**
     * @description Sends Email Alerts when Opps are moved to specific Stages
     * @param List<Opportunity> new Opps List
     * @param Map<Id, Opportunity>  Map<Id, Opportunity> old Opps Map
     */
    public static void sendStageChangesNotifications(List<Opportunity> newList, Map<Id, Opportunity> oldMap) {
        Map<Stages, List<Opportunity>> stageOppsMap = new Map<Stages, List<Opportunity>>();

        for (Opportunity opp : newList) {
            if (TriggerUtils.fieldHasChanged(oldMap?.get(opp.Id), opp, 'StageName')) {
                if (opp.StageName == AWAL_Constants.OPPORTUNITY_STAGE_PASSED_TO_FINANCE) {
                    if (!stageOppsMap.containsKey(Stages.PASSED_TO_FINANCE)) {
                        stageOppsMap.put(Stages.PASSED_TO_FINANCE, new List<Opportunity>());
                    }
                    stageOppsMap.get(Stages.PASSED_TO_FINANCE).add(opp);
                } else if (opp.StageName == AWAL_Constants.OPPORTUNITY_STAGE_PASSED_TO_FINANCE_APPROVER) {
                    if (!stageOppsMap.containsKey(Stages.PASSED_TO_FINANCE_APPROVER)) {
                        stageOppsMap.put(Stages.PASSED_TO_FINANCE_APPROVER, new List<Opportunity>());
                    }
                    stageOppsMap.get(Stages.PASSED_TO_FINANCE_APPROVER).add(opp);
                } else if (opp.StageName == AWAL_Constants.OPPORTUNITY_STAGE_ANALYSIS_COMPLETE) {
                    if (!stageOppsMap.containsKey(Stages.FINANCE_ANALYSIS_COMPLETE)) {
                        stageOppsMap.put(Stages.FINANCE_ANALYSIS_COMPLETE, new List<Opportunity>());
                    }
                    stageOppsMap.get(Stages.FINANCE_ANALYSIS_COMPLETE).add(opp);
                } else if (opp.StageName == AWAL_Constants.OPPORTUNITY_STAGE_PASSED_TO_APPROVER) {
                    if (!stageOppsMap.containsKey(Stages.PASSED_TO_APPROVER)) {
                        stageOppsMap.put(Stages.PASSED_TO_APPROVER, new List<Opportunity>());
                    }
                    stageOppsMap.get(Stages.PASSED_TO_APPROVER).add(opp);
                } else if (opp.StageName == AWAL_Constants.OPPORTUNITY_STAGE_PASSED_TO_BA) {
                    if (!stageOppsMap.containsKey(Stages.PASSED_TO_BA)) {
                        stageOppsMap.put(Stages.PASSED_TO_BA, new List<Opportunity>());
                    }
                    stageOppsMap.get(Stages.PASSED_TO_BA).add(opp);
                } else if (opp.StageName == AWAL_Constants.OPPORTUNITY_STAGE_APPROVED) {
                    if (!stageOppsMap.containsKey(Stages.APPROVED)) {
                        stageOppsMap.put(Stages.APPROVED, new List<Opportunity>());
                    }
                    stageOppsMap.get(Stages.APPROVED).add(opp);
                } else if (opp.StageName == AWAL_Constants.OPPORTUNITY_STAGE_CLOSED_LOST) {
                    if (!stageOppsMap.containsKey(Stages.CLOSED_LOST)) {
                        stageOppsMap.put(Stages.CLOSED_LOST, new List<Opportunity>());
                    }
                    stageOppsMap.get(Stages.CLOSED_LOST).add(opp);
                }
            }
        }

        if (!stageOppsMap.isEmpty()) {
            awalEmailTemplate = [
                SELECT HtmlValue, Body
                FROM EmailTemplate
                WHERE DeveloperName = :AWAL_Constants.STAGE_CHANGE_EMAIL_TEMPLATE
            ];

            Map<Id, MasterApprovers__c> approversMap = new Map<Id, MasterApprovers__c>(
                MasterApproverUtils.getRecordsByCustomerAndTeam(
                    new Set<String>{ AWAL_Constants.AWAL },
                    new Set<String>{ AWAL_Constants.MASTER_APPROVERS_APPROVERS_TEAM }
                )
            );
            Set<Stages> stagesThatNeedDealmakersEmails = new Set<Stages>{
                Stages.PASSED_TO_FINANCE_APPROVER,
                Stages.FINANCE_ANALYSIS_COMPLETE,
                Stages.APPROVED,
                Stages.CLOSED_LOST
            };

            Map<String, String> dealMakersEmailMap = null;

            for (Stages key : stageOppsMap.keySet()) {
                if (stagesThatNeedDealmakersEmails.contains(key) && dealMakersEmailMap == null) {
                    dealMakersEmailMap = getDealMakersEmailAddresses(newList);
                }
                createEmailMessages(stageOppsMap.get(key), key, approversMap, dealMakersEmailMap);
            }
            Messaging.sendEmail(emailsToSend);
        }
    }

    /**
     * @description Sends Email Alerts when Opps are moved to specific Stages
     * @param List<Opportunity> Opps moving to the specific Stage
     * @param Stages    the Stage these Opps are moving to
     * @param Map<Id, MasterApprovers__c>   master approvers map
     */
    private static void createEmailMessages(
        List<Opportunity> opps,
        Stages newStage,
        Map<Id, MasterApprovers__c> approversMap,
        Map<String, String> dealMakersEmailMap
    ) {
        String subject;
        List<String> recipients = new List<String>{ UserInfo.getUserEmail() };
        String message;

        switch on newStage {
            when PASSED_TO_FINANCE {
                subject = ConfigUtils.getConfigString(ConfigUtils.AWAL_ALERT_FINANCE_SUBJECT);
                recipients.addAll(ConfigUtils.getConfigMultipleStrings(ConfigUtils.AWAL_ALERT_FINANCE_RECIPIENTS));
                message = ConfigUtils.getConfigString(ConfigUtils.AWAL_ALERT_FINANCE_MESSAGE);
            }
            when PASSED_TO_FINANCE_APPROVER {
                subject = ConfigUtils.getConfigString(ConfigUtils.AWAL_ALERT_FINANCE_APPROVER_SUBJECT);
                recipients.addAll(
                    ConfigUtils.getConfigMultipleStrings(ConfigUtils.AWAL_ALERT_FINANCE_APPROVER_RECIPIENTS)
                );
                message = ConfigUtils.getConfigString(ConfigUtils.AWAL_ALERT_FINANCE_APPROVER_MESSAGE);
            }
            when FINANCE_ANALYSIS_COMPLETE {
                subject = ConfigUtils.getConfigString(ConfigUtils.AWAL_ALERT_ANALYSIS_CMPLTD_SUBJECT);
                recipients.addAll(
                    ConfigUtils.getConfigMultipleStrings(ConfigUtils.AWAL_ALERT_ANALYSIS_CMPLTD_RECIPIENTS)
                );
                message = ConfigUtils.getConfigString(ConfigUtils.AWAL_ALERT_ANALYSIS_CMPLTD_MESSAGE);
            }
            when PASSED_TO_APPROVER {
                subject = ConfigUtils.getConfigString(ConfigUtils.AWAL_ALERT_APPROVER_SUBJECT);
                recipients.addAll(ConfigUtils.getConfigMultipleStrings(ConfigUtils.AWAL_ALERT_APPROVER_RECIPIENTS));
                message = ConfigUtils.getConfigString(ConfigUtils.AWAL_ALERT_APPROVER_MESSAGE);
            }
            when APPROVED {
                subject = ConfigUtils.getConfigString(ConfigUtils.AWAL_ALERT_APPROVED_SUBJECT);
                recipients.addAll(ConfigUtils.getConfigMultipleStrings(ConfigUtils.AWAL_ALERT_APPROVED_RECIPIENTS));
                message = ConfigUtils.getConfigString(ConfigUtils.AWAL_ALERT_APPROVED_MESSAGE);
            }
            when PASSED_TO_BA {
                subject = ConfigUtils.getConfigString(ConfigUtils.AWAL_ALERT_BA_SUBJECT);
                recipients.addAll(ConfigUtils.getConfigMultipleStrings(ConfigUtils.AWAL_ALERT_BA_RECIPIENTS));
                message = ConfigUtils.getConfigString(ConfigUtils.AWAL_ALERT_BA_MESSAGE);
            }
            when CLOSED_LOST {
                subject = ConfigUtils.getConfigString(ConfigUtils.AWAL_ALERT_CLOSED_LOST_SUBJECT);
                recipients.addAll(ConfigUtils.getConfigMultipleStrings(ConfigUtils.AWAL_ALERT_CLOSED_LOST_RECIPIENTS));
                message = ConfigUtils.getConfigString(ConfigUtils.AWAL_ALERT_CLOSED_LOST_MESSAGE);
            }
        }

        for (Opportunity opp : opps) {
            Messaging.SingleEmailMessage emailMessage = new Messaging.SingleEmailMessage();

            if (
                newStage == Stages.PASSED_TO_APPROVER ||
                newStage == Stages.APPROVED ||
                newStage == Stages.CLOSED_LOST
            ) {
                EmailUtils.addRecipient(recipients, approversMap.get(opp.MasterApproverId__c)?.User__r.Email);

                subject = subject.replace(
                    EMAIL_TOKEN_APPROVERS_INITIALS,
                    approversMap.get(opp.MasterApproverId__c)?.ApproverInitials__c ?? ''
                );
            }

            if (
                newStage == Stages.PASSED_TO_FINANCE_APPROVER ||
                newStage == Stages.FINANCE_ANALYSIS_COMPLETE ||
                newStage == Stages.CLOSED_LOST ||
                newStage == Stages.APPROVED
            ) {
                if (dealMakersEmailMap.containsKey(opp.LeadDealMaker__c)) {
                    EmailUtils.addRecipient(recipients, dealMakersEmailMap.get(opp.LeadDealMaker__c));
                }
                if (String.isNotBlank(opp.OtherDealMakers__c)) {
                    for (String otherDealMaker : opp.OtherDealMakers__c.split(';')) {
                        if (dealMakersEmailMap.containsKey(otherDealMaker)) {
                            EmailUtils.addRecipient(recipients, dealMakersEmailMap.get(otherDealMaker));
                        }
                    }
                }
            }

            subject = subject.replace(EMAIL_TOKEN_OPPORTUNITY_NAME, opp.Name);

            emailMessage.setToAddresses(
                opp.IsPrivate
                    ? EmailUtils.removeRecipient(recipients, RECIPIENTS_TO_REMOVE_FOR_PRIVATE_OPPS)
                    : recipients
            );
            emailMessage.setSubject(subject);
            emailMessage.setHtmlBody(replaceHtmlBody(opp, message, newStage));

            emailsToSend.add(emailMessage);
        }
    }

    /**
     * @description Gets email addresses from Lead Deal Maker and Other Deal Makers names
     * @param List<Opportunity> Opps to retrieve the info from
     * @return Map<String, String>  DealMaker's <Name,Email> map
     */
    public static Map<String, String> getDealMakersEmailAddresses(List<Opportunity> opps) {
        Set<String> dealMakers = new Set<String>();
        for (Opportunity opp : opps) {
            if (String.isNotBlank(opp.LeadDealMaker__c)) {
                dealMakers.add(opp.LeadDealMaker__c);
            }
            if (String.isNotBlank(opp.OtherDealMakers__c)) {
                dealMakers.addAll(opp.OtherDealMakers__c.split(';'));
            }
        }

        Map<String, String> dealMakersAddresses = new Map<String, String>();
        if (!dealMakers.isEmpty()) {
            for (User dealMaker : [SELECT Name, Email FROM User WHERE IsActive = TRUE AND Name IN :dealMakers]) {
                dealMakersAddresses.put(dealMaker.Name, dealMaker.Email);
            }
        }
        return dealMakersAddresses;
    }

    /**
     * @description Replaces tokens in the email template with predefined values and Opp's data
     * @param Opportunity   Opp the email refers to
     * @param String    message to use for this specific Stage
     * @param Stages    the Stage this Opp is moving to
     * @return String   html body
     */
    private static String replaceHtmlBody(Opportunity opp, String message, Stages newStage) {
        String htmlBody = awalEmailTemplate.HtmlValue.replace(EMAIL_TOKEN_MESSAGE, message);
        List<Object> viewLinkParameters = new List<Object>{ URL.getOrgDomainUrl().toExternalForm(), opp.Id };
        htmlBody = htmlBody.replace(
            EMAIL_TOKEN_URL,
            String.format(AWAL_Constants.VIEW_OPPORTUNITY_LINK, viewLinkParameters)
        );

        List<String> fields = new List<String>();
        fields.add('<ul>');

        List<Schema.FieldSetMember> fieldSetMembersToShow;
        if (!fieldSetMembersMap.containsKey(newStage)) {
            fieldSetMembersToShow = fieldSetMembers;
        } else {
            fieldSetMembersToShow = fieldSetMembersMap.get(newStage);
        }

        if (!opp.Is_Amendment__c) {
            for (Integer i = fieldSetMembersToShow.size() - 1; i >= 0; i--) {
                if (fieldSetMembersToShow[i].getFieldPath().equals('AWAL_Deal_Lawyer__c')) {
                    fieldSetMembersToShow.remove(i);
                    break;
                }
            }
        }

        for (Schema.FieldSetMember fieldSetMember : fieldSetMembersToShow) {
            String fieldType = fieldSetMember.getType().toString();
            String value = FormatUtils.formatFieldsToString(opp, fieldSetMember.getFieldPath(), fieldType);
            fields.add('<li><b>' + fieldSetMember.getLabel() + '</b>: ' + value + '</li>');
        }
        fields.add('</ul>');
        return htmlBody.replace(EMAIL_TOKEN_FIELDS, String.join(fields, ''));
    }

    /**
     * @description Retrieves all active AWAL users and formats them as picklist options.
     * @return List<UserOption>
     */
    @AuraEnabled(cacheable=true)
    public static List<UserOption> getAwalActiveUsers() {
        List<UserOption> users = new List<UserOption>();

        for (String recipient : ConfigUtils.getConfigMultipleStrings(ConfigUtils.AWAL_SDU_EXTRA_RECIPIENTS)) {
            UserOption user = new UserOption();
            user.label = recipient;
            user.value = recipient;
            users.add(user);
        }

        for (User u : [
            SELECT Name, Email
            FROM User
            WHERE IsActive = TRUE AND Profile.Name = :AWAL_Constants.AWAL
            ORDER BY Name
        ]) {
            UserOption user = new UserOption();
            user.label = u.Name + ' (' + u.Email + ')';
            user.value = u.Email;
            users.add(user);
        }
        return users;
    }

    public class UserOption {
        @AuraEnabled
        public String label;
        @AuraEnabled
        public String value;
    }
}