/***********************************************************************************************************
 * Name         : ContentDocumentLinkTriggerHelper
 * Purpose      : Implementation for methods called by ContentDocumentLinkTriggerHandler
 **********************************************************************************************************/
public with sharing class ContentDocumentLinkTriggerHelper {
    public static final String AWAL_RT_ID = Schema.SObjectType.Opportunity.getRecordTypeInfosByDeveloperName()
        .get(AWAL_Constants.AWAL)
        .getRecordTypeId();

    /**
     * @description updates the HasAttachedFile__c to true when the user uploads a pre existing document
     * @param newList    new List values
     */
    public static void updateOpportunitiesOnAttachmentAdd(List<ContentDocumentLink> links) {
        Set<Id> opportunityIds = new Set<Id>();

        for (ContentDocumentLink cdl : links) {
            if (cdl.LinkedEntityId.getSObjectType() == Opportunity.SObjectType) {
                opportunityIds.add(cdl.LinkedEntityId);
            }
        }

        if (!opportunityIds.isEmpty()) {
            List<Opportunity> oppsToUpdate = [
                SELECT Id, HasAttachments__c
                FROM Opportunity
                WHERE Id IN :opportunityIds AND HasAttachments__c = FALSE AND RecordTypeId = :AWAL_RT_ID
            ];

            for (Opportunity opp : oppsToUpdate) {
                opp.HasAttachments__c = true;
            }

            if (!oppsToUpdate.isEmpty()) {
                TriggerControl.disableTrigger(OpportunityTriggerHandler.SELF);
                update oppsToUpdate;
                TriggerControl.enableTrigger(OpportunityTriggerHandler.SELF);
            }
        }
    }

    /**
     * @description updates the HasAttachedFile__c to true when the user uploads a new document
     * @param newList    new List values
     */
    public static void updatePaymentsOnAttachmentAdd(List<ContentDocumentLink> links) {
        Set<Id> paymentIds = new Set<Id>();

        for (ContentDocumentLink cdl : links) {
            if (cdl.LinkedEntityId.getSObjectType() == AdvancePayment__c.SObjectType) {
                paymentIds.add(cdl.LinkedEntityId);
            }
        }

        if (!paymentIds.isEmpty()) {
            List<AdvancePayment__c> paymentsToUpdate = [
                SELECT Id, HasAttachedFile__c
                FROM AdvancePayment__c
                WHERE Id IN :paymentIds AND HasAttachedFile__c = FALSE
            ];

            for (AdvancePayment__c opp : paymentsToUpdate) {
                opp.HasAttachedFile__c = true;
            }

            if (!paymentsToUpdate.isEmpty()) {
                update paymentsToUpdate;
            }
        }
    }

    /**
     * @description Handles ContentDocumentLink deletions for AWAL Opportunities:
     *  1) Blocks deletion (addError) when removing the link(s) would leave an AWAL Opportunity that
     *     requires the P&L tool (IsPAndLToolAttached__c = 'Yes') with zero remaining ContentDocumentLinks.
     *  2) Updates Opportunity.HasAttachments__c to false when, after the deletions, the Opportunity would
     *     have zero remaining ContentDocumentLinks (only for AWAL record type, and only when not blocked).
     * @param links List<ContentDocumentLink> being deleted (Trigger.old in before delete).
     */
    public static void handleDeletionsForAWAL(List<ContentDocumentLink> links) {
        Map<Id, List<ContentDocumentLink>> oppIdToLinksToDelete = new Map<Id, List<ContentDocumentLink>>();

        for (ContentDocumentLink cdl : links) {
            if (cdl.LinkedEntityId == null || cdl.LinkedEntityId.getSObjectType() != Opportunity.SObjectType) {
                continue;
            }

            if (!oppIdToLinksToDelete.containsKey(cdl.LinkedEntityId)) {
                oppIdToLinksToDelete.put(cdl.LinkedEntityId, new List<ContentDocumentLink>());
            }
            oppIdToLinksToDelete.get(cdl.LinkedEntityId).add(cdl);
        }

        if (oppIdToLinksToDelete.isEmpty()) {
            return;
        }

        Map<Id, Opportunity> awalOppsById = new Map<Id, Opportunity>(
            [
                SELECT Id, HasAttachments__c, IsPAndLToolAttached__c
                FROM Opportunity
                WHERE Id IN :oppIdToLinksToDelete.keySet() AND RecordTypeId = :OpportunityTriggerHelper.AWAL_RECTYPE_ID
            ]
        );

        if (awalOppsById.isEmpty()) {
            return;
        }

        Set<Id> linkIdsBeingDeletedForAwalOpps = new Set<Id>();
        for (Id oppId : awalOppsById.keySet()) {
            List<ContentDocumentLink> cdlsForOpp = oppIdToLinksToDelete.get(oppId);

            for (ContentDocumentLink cdl : cdlsForOpp) {
                linkIdsBeingDeletedForAwalOpps.add(cdl.Id);
            }
        }

        if (linkIdsBeingDeletedForAwalOpps.isEmpty()) {
            return;
        }

        Map<Id, Integer> remainingLinksAfterDeleteByOppId = new Map<Id, Integer>();
        for (AggregateResult ar : [
            SELECT LinkedEntityId oppId, COUNT(Id) total
            FROM ContentDocumentLink
            WHERE LinkedEntityId IN :awalOppsById.keySet() AND Id NOT IN :linkIdsBeingDeletedForAwalOpps
            GROUP BY LinkedEntityId
        ]) {
            remainingLinksAfterDeleteByOppId.put((Id) ar.get('oppId'), (Integer) ar.get('total'));
        }

        List<Opportunity> oppsToUpdate = new List<Opportunity>();

        for (Opportunity opp : awalOppsById.values()) {
            Integer remainingLinksAfterThisDelete = (remainingLinksAfterDeleteByOppId.get(opp.Id) ?? 0);

            if (remainingLinksAfterThisDelete == 0) {
                if (opp.IsPAndLToolAttached__c == 'Yes') {
                    for (ContentDocumentLink cdl : oppIdToLinksToDelete.get(opp.Id)) {
                        cdl.addError(AWAL_Constants.ERROR_MSG_PL_FILE_REMOVE_NOT_ALLOWED);
                    }
                    continue;
                }

                if (opp.HasAttachments__c) {
                    oppsToUpdate.add(new Opportunity(Id = opp.Id, HasAttachments__c = false));
                }
            }
        }

        if (!oppsToUpdate.isEmpty()) {
            TriggerControl.disableTrigger(OpportunityTriggerHandler.SELF);
            update oppsToUpdate;
            TriggerControl.enableTrigger(OpportunityTriggerHandler.SELF);
        }
    }
}