/***********************************************************************************************************
 * Name         : LeadTriggerHelper
 * Purpose      : Implementation for methods called by LeadTriggerHandler
 **********************************************************************************************************/
public with sharing class LeadTriggerHelper {
    public static final Id CA_RECTYPE_ID;
    public static final Id AAA_RECTYPE_ID;

    static {
        Map<String, Schema.RecordTypeInfo> recordTypeInfosMap = Schema.SObjectType.Lead.getRecordTypeInfosByDeveloperName();
        for (String developerName : recordTypeInfosMap.keySet()) {
            Id recordTypeId = recordTypeInfosMap.get(developerName).getRecordTypeId();
            if (developerName == CA_Constants.RECORD_TYPE_CA) {
                CA_RECTYPE_ID = recordTypeId;
            } else if (developerName == AAA_Constants.LEAD_RECORD_TYPE) {
                AAA_RECTYPE_ID = recordTypeId;
            }
        }
    }

    /**
     * @description Filter Leads by record type
     * @param leads          Trigger.new
     * @param recordTypes   Set of record type Ids
     * @return List<Lead> filtered leads
     */
    public static List<Lead> filterLeads(List<Lead> leads, Set<Id> recordTypes) {
        List<Lead> filteredLeads = new List<Lead>();
        for (Lead l : leads) {
            if (recordTypes.contains(l.RecordTypeId)) {
                filteredLeads.add(l);
            }
        }
        return filteredLeads;
    }

    /**
     * @description Remove leads from the oldMap that are not in the filteredleads
     * @param leads          List of filtered leads
     * @param oldMapAll     Trigger.oldMap
     * @return List<Lead> filtered leads
     */
    public static Map<Id, Lead> getFilteredOldMap(List<Lead> leads, Map<Id, Lead> oldMapAll) {
        Map<Id, Lead> oldMap = new Map<Id, Lead>();
        for (Lead l : leads) {
            oldMap.put(l.Id, oldMapAll.get(l.Id));
        }
        return oldMap;
    }

    /**
     * @description Calculates the Previously Rejected Count value based on
     *  existing unqualified Leads matching by Email and/or Spotify URL
     * @param List<Lead>    Lead records being inserted
     */
    public static void calculatePreviouslyRejectedCount(List<Lead> newList) {
        Set<String> spotifyUrls = new Set<String>();
        Set<String> emails = new Set<String>();
        Set<Lead> leads = new Set<Lead>();
        for (Lead newLead : newList) {
            if (String.isNotBlank(newLead.SpotifyUrl__c)) {
                spotifyUrls.add(newLead.SpotifyUrl__c);
                leads.add(newLead);
            }
            if (String.isNotBlank(newLead.Email)) {
                emails.add(newLead.Email);
                leads.add(newLead);
            }
        }

        if (!spotifyUrls.isEmpty() || !emails.isEmpty()) {
            List<AggregateResult> previouslyRejectedLeads = [
                SELECT Email, SpotifyUrl__c, count(Id) Times
                FROM Lead
                WHERE
                    Status = :AAA_Constants.LEAD_STATUS_UNQUALIFIED
                    AND (Email IN :emails
                    OR SpotifyUrl__c IN :spotifyUrls)
                GROUP BY Email, SpotifyUrl__c
            ];

            if (!previouslyRejectedLeads.isEmpty()) {
                for (Lead newLead : leads) {
                    for (AggregateResult previouslyRejectedLead : previouslyRejectedLeads) {
                        if (
                            newLead.Email == previouslyRejectedLead.get('Email') ||
                            newLead.SpotifyUrl__c == previouslyRejectedLead.get('SpotifyUrl__c')
                        ) {
                            newLead.Previously_Rejected_Count__c += Integer.valueOf(
                                previouslyRejectedLead.get('Times')
                            );
                        }
                    }
                }
            }
        }
    }

    /**
     * @description Whenever a Lead is updated to Unqualified, we should look for other active
     *  Leads that match Email and/or Spotify URL and increase their Previously Rejected
     *  Count value
     * @param List<Lead>    Lead List with new values
     * @param Map<Id, Lead> Lead Map with old values
     */
    public static void updatePreviouslyRejectedCount(List<Lead> newList, Map<Id, Lead> oldMap) {
        Set<Id> leadIds = new Set<Id>();
        for (Lead newLead : newList) {
            if (
                newLead.Status == AAA_Constants.LEAD_STATUS_UNQUALIFIED &&
                oldMap.get(newLead.Id).Status != newLead.Status
            ) {
                leadIds.add(newLead.Id);
            }
        }
        if (!leadIds.isEmpty()) {
            updatePreviouslyRejectedCountFuture(leadIds);
        }
    }

    /**
     * @description Future method to look for other active Leads that match
     *  Email and/or Spotify URL and increase their Previously Rejected
     *  Count value
     * @param Set<Id>    Lead Ids
     */
    @Future
    public static void updatePreviouslyRejectedCountFuture(Set<Id> leadIds) {
        List<Lead> updatedLeads = [
            SELECT Id, Email, SpotifyUrl__c
            FROM Lead
            WHERE Id IN :leadIds AND Status = :AAA_Constants.LEAD_STATUS_UNQUALIFIED
        ];
        Set<String> spotifyUrls = new Set<String>();
        Set<String> emails = new Set<String>();
        for (Lead updatedLead : updatedLeads) {
            if (String.isNotBlank(updatedLead.SpotifyUrl__c)) {
                spotifyUrls.add(updatedLead.SpotifyUrl__c);
            }
            if (String.isNotBlank(updatedLead.Email)) {
                emails.add(updatedLead.Email);
            }
        }

        if (!spotifyUrls.isEmpty() || !emails.isEmpty()) {
            List<Lead> leadsToUpdate = [
                SELECT Id, Email, SpotifyUrl__c, Previously_Rejected_Count__c
                FROM Lead
                WHERE
                    Status NOT IN (:AAA_Constants.LEAD_STATUS_UNQUALIFIED, :AAA_Constants.LEAD_STATUS_QUALIFIED)
                    AND (Email IN :emails
                    OR SpotifyUrl__c IN :spotifyUrls)
            ];
            if (!leadsToUpdate.isEmpty()) {
                for (Lead leadToUpdate : leadsToUpdate) {
                    for (Lead updatedLead : updatedLeads) {
                        if (
                            updatedLead.Email == leadToUpdate.Email ||
                            updatedLead.SpotifyUrl__c == leadToUpdate.SpotifyUrl__c
                        ) {
                            leadToUpdate.Previously_Rejected_Count__c++;
                        }
                    }
                }
                TriggerControl.disableTrigger(LeadTriggerHandler.SELF);
                Database.update(leadsToUpdate, false);
            }
        }
    }
}
