/***********************************************************************************************************
 * Name         : AWAL_SduHelper
 * Purpose      : Controller class for LWC awalSdu
 **********************************************************************************************************/
public without sharing class AWAL_SduHelper {
    private static List<String> CUMULATIVE_FIELDS;
    @TestVisible
    static Blob testPdfBlob;

    /**
     * Returns a wrapper containing SDU, Opportunity, grouped, boolean, and picklist fields for use in the SDU UI.
     * Combines fields from constants for use in Lightning components.
     * @return FieldsWrapper containing all relevant field lists
     */
    @AuraEnabled(cacheable=true)
    public static FieldsWrapper getFields() {
        try {
            List<String> allOppFields = new List<String>(AWAL_SduConstants.OPP_FIELDS);
            allOppFields.addAll(AWAL_SduConstants.OPP_FORMULA_FIELDS);
            allOppFields.addAll(AWAL_SduConstants.OPP_TO_LABEL);
            return new FieldsWrapper(
                AWAL_SduConstants.SDU_FIELDS,
                allOppFields,
                AWAL_SduConstants.GROUPED_FIELDS,
                AWAL_SduConstants.OPP_BOOLEAN_FIELDS,
                AWAL_SduConstants.SDU_PICKLISTS
            );
        } catch (Exception ex) {
            ErrorLogger.logError(ex);
            return null;
        }
    }

    /**
     * Retrieves the Opportunity_SDU__c record for a given Opportunity Id.
     * @param oppId The Id of the Opportunity
     * @return The Opportunity_SDU__c record, or null if not found
     */
    @AuraEnabled
    public static Opportunity_SDU__c getSDU(Id oppId) {
        try {
            String query =
                'SELECT ' +
                String.join(AWAL_SduConstants.SDU_FIELDS, ',') +
                ' FROM Opportunity_SDU__c' +
                ' WHERE Opportunity__c = :oppId';
            List<Opportunity_SDU__c> sdus = Database.query(query);
            return sdus.isEmpty() ? null : sdus[0];
        } catch (Exception ex) {
            throw new AuraHandledException('An error occurred while retrieving the SDU record.');
        }
    }

    /**
     * Saves (upserts) the Opportunity_SDU__c record. Clears details if 'Relevant_Recordings__c' is not 'Other'.
     * @param record The Opportunity_SDU__c record to save
     * @return The Id of the saved record
     */
    @AuraEnabled
    public static Id saveSDU(Opportunity_SDU__c record) {
        try {
            if (record.Relevant_Recordings__c != 'Other') {
                record.Relevant_Recordings_Details__c = null;
            }
            upsert record;
            return record.Id;
        } catch (Exception ex) {
            ErrorLogger.logError(ex);
            return null;
        }
    }

    /**
     * Sends an email with the SDU PDF attached to the specified recipients.
     * @param oppName The name of the Opportunity
     * @param base64Pdf The PDF file as a base64-encoded string
     * @param subject The email subject
     * @param toAddresses List of recipient email addresses
     * @param ccAddresses List of CC email addresses
     */
    @AuraEnabled
    public static String sendEmail(
        Map<String, Object> oppRecord,
        String base64Pdf,
        String subject,
        List<String> toAddresses,
        List<String> ccAddresses
    ) {
        try {
            Blob pdfBlob = EncodingUtil.base64Decode(base64Pdf);
            Messaging.EmailFileAttachment att = new Messaging.EmailFileAttachment();
            att.setFileName('SDU - ' + oppRecord.get('Name') + '.pdf');
            att.setContentType('application/pdf');
            att.setBody(pdfBlob);
            List<String> toList = new List<String>();
            for (String addr : toAddresses) {
                EmailUtils.addRecipient(toList, addr.trim());
            }
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            mail.setToAddresses(toList);
            mail.setCcAddresses(ccAddresses);
            mail.setSubject(subject);
            String body =
                '<p><strong>CONFIDENTIAL</strong></p>' +
                '<p>Hi all,</p>' +
                '<p>Please find the attached SDU summary for the following opportunity: ' +
                oppRecord.get('LegalEntityNameId__c') +
                '/' +
                oppRecord.get('ArtistOrLabelName__r.Name') +
                '.</p>' +
                '<p><strong>Client Admin:</strong> Please reach out to BA should you have any questions.</p>' +
                '<p>Thank you</p>';
            mail.setPlainTextBody(body);
            mail.setHtmlBody(body);
            mail.setFileAttachments(new List<Messaging.EmailFileAttachment>{ att });
            Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{ mail });
            return 'SENT';
        } catch (Exception ex) {
            ErrorLogger.logError(ex);
            return null;
        }
    }

    /**
     * Generates a PDF for the SDU, upserts it as a ContentVersion, and returns the base64-encoded PDF.
     * Ensures BA team users receives viewer access to the document
     * @param oppId The Opportunity Id
     * @param oppName The Opportunity name
     * @return The base64-encoded PDF string
     */
    @AuraEnabled
    public static String upsertPdfAsContentVersion(Id oppId, String oppName) {
        try {
            PageReference pageRef = Page.AWAL_SduPdf;
            pageRef.getParameters().put('oppId', String.valueOf(oppId));

            Blob pdfBlob = (Test.isRunningTest() && testPdfBlob != null) ? testPdfBlob : pageRef.getContentAsPDF();

            Id existingDocId = findExistingPdfDocumentId(oppId);

            ContentVersion cv = new ContentVersion();
            cv.VersionData = pdfBlob;
            cv.Title = 'SDU - ' + oppName;
            cv.Description = 'SDU';
            cv.PathOnClient = String.valueOf(oppId) + '.pdf';
            cv.SharingPrivacy = 'P';

            if (existingDocId != null) {
                cv.ContentDocumentId = existingDocId;
            } else {
                cv.FirstPublishLocationId = oppId;
            }

            insert cv;

            Id docId = (existingDocId != null)
                ? existingDocId
                : [SELECT ContentDocumentId FROM ContentVersion WHERE Id = :cv.Id].ContentDocumentId;

            Set<Id> userIds = new Set<Id>();

            PermissionSet baPermission = [
                SELECT Id
                FROM PermissionSet
                WHERE Name = :AWAL_Constants.PERMISSION_SET_SDU
                LIMIT 1
            ];

            for (PermissionSetAssignment psa : [
                SELECT AssigneeId, Assignee.IsActive
                FROM PermissionSetAssignment
                WHERE PermissionSetId = :baPermission.Id
            ]) {
                if (psa.Assignee.IsActive) {
                    userIds.add(psa.AssigneeId);
                }
            }

            if (!userIds.isEmpty()) {
                Set<Id> usersAlreadyLinked = new Set<Id>();
                for (ContentDocumentLink cdl : [
                    SELECT LinkedEntityId
                    FROM ContentDocumentLink
                    WHERE ContentDocumentId = :docId AND LinkedEntityId IN :userIds
                ]) {
                    usersAlreadyLinked.add(cdl.LinkedEntityId);
                }

                List<ContentDocumentLink> linksToInsert = new List<ContentDocumentLink>();
                for (Id userId : userIds) {
                    if (!usersAlreadyLinked.contains(userId)) {
                        linksToInsert.add(
                            new ContentDocumentLink(ContentDocumentId = docId, LinkedEntityId = userId, ShareType = 'V')
                        );
                    }
                }
                if (!linksToInsert.isEmpty()) {
                    insert linksToInsert;
                }
            }

            return EncodingUtil.base64Encode(pdfBlob);
        } catch (Exception ex) {
            ErrorLogger.logError(ex);
            return null;
        }
    }

    /**
     * Finds the ContentDocumentId of the latest PDF file attached to the Opportunity.
     * @param oppId The Opportunity Id
     * @return The ContentDocumentId if found, otherwise null
     */
    private static Id findExistingPdfDocumentId(Id oppId) {
        List<ContentDocumentLink> links = [
            SELECT ContentDocumentId
            FROM ContentDocumentLink
            WHERE LinkedEntityId = :oppId
        ];
        if (links.isEmpty()) {
            return null;
        }

        Set<Id> docIds = new Set<Id>();
        for (ContentDocumentLink link : links) {
            docIds.add(link.ContentDocumentId);
        }

        List<ContentVersion> latestFile = [
            SELECT ContentDocumentId, CreatedDate, FileType
            FROM ContentVersion
            WHERE IsLatest = TRUE AND ContentDocumentId IN :docIds AND FileType = 'PDF'
            ORDER BY CreatedDate DESC
            LIMIT 1
        ];
        if (!latestFile.isEmpty()) {
            return latestFile[0].ContentDocumentId;
        } else {
            return null;
        }
    }

    /**
     * Returns a snapshot of the Opportunity, including amended fields and cumulative values for amendments.
     * @param oppId The Opportunity Id
     * @return SnapshotWrapper containing the snapshot, amended fields, and cumulative map
     */
    @AuraEnabled
    public static SnapshotWrapper getSnapshot(Id oppId) {
        try {
            String CLOSED_WON = AWAL_Constants.OPPORTUNITY_STAGE_CLOSED_WON;

            List<String> toLabelFields = new List<String>();
            for (String field : AWAL_SduConstants.OPP_TO_LABEL) {
                toLabelFields.add('toLabel(' + field + ') ' + field);
            }

            String query =
                'SELECT ' +
                String.join(AWAL_SduConstants.OPP_FIELDS, ',') +
                ' , ' +
                String.join(AWAL_SduConstants.OPP_FORMULA_FIELDS, ',') +
                ' , ' +
                String.join(toLabelFields, ',') +
                ' , Parent_Opportunity__c' +
                ' FROM Opportunity' +
                ' WHERE Id = :oppId';
            Opportunity currentOpp = Database.query(query);

            if (!currentOpp.Is_Amendment__c) {
                return new SnapshotWrapper(currentOpp, new List<String>(), new Map<String, String>());
            } else {
                List<String> amendedFields = new List<String>();
                List<String> CUMULATIVE_FIELDS = new List<String>(AWAL_SduConstants.OPP_CUMULATIVE_CURRENCY_FIELDS);
                CUMULATIVE_FIELDS.addAll(AWAL_SduConstants.OPP_CUMULATIVE_NUMBER_FIELDS);

                String parentOpp = currentOpp.Parent_Opportunity__c;
                query =
                    'SELECT ' +
                    String.join(AWAL_SduConstants.OPP_FIELDS, ',') +
                    ' , ' +
                    String.join(AWAL_SduConstants.OPP_FORMULA_FIELDS, ',') +
                    ' , ' +
                    String.join(toLabelFields, ',') +
                    ' FROM Opportunity WHERE (Parent_Opportunity__c = :parentOpp OR Id = :parentOpp)' +
                    ' AND Id != :oppId' +
                    ' AND StageName = :CLOSED_WON ORDER BY CreatedDate ASC';

                List<Opportunity> opps = Database.query(query);
                Map<String, List<Object>> cumulativeFieldsMap = new Map<String, List<Object>>();
                for (String field : CUMULATIVE_FIELDS) {
                    cumulativeFieldsMap.put(field, new List<Object>());
                }

                Opportunity baselineSnapshot = new Opportunity();

                for (Opportunity opp : opps) {
                    for (String field : AWAL_SduConstants.OPP_FIELDS) {
                        Object fieldValue;
                        fieldValue = SchemaUtils.getFieldValueFromSObject(opp, field);

                        if (fieldValue != null) {
                            if (field.contains('.')) {
                                List<String> composedName = field.split('\\.');
                                String relationshipName = composedName[0];
                                String relationshipField = composedName[1];
                                SchemaUtils.populateRelated(
                                    baselineSnapshot,
                                    relationshipName,
                                    new Map<String, Object>{ relationshipField => fieldValue }
                                );
                            } else {
                                baselineSnapshot.put(field, fieldValue);
                            }
                        }

                        if (CUMULATIVE_FIELDS.contains(field)) {
                            cumulativeFieldsMap.get(field).add(fieldValue);
                        }
                    }
                    for (String field : AWAL_SduConstants.OPP_FORMULA_FIELDS) {
                        if (CUMULATIVE_FIELDS.contains(field)) {
                            cumulativeFieldsMap.get(field).add(opp.get(field));
                        }
                    }
                }

                for (String field : CUMULATIVE_FIELDS) {
                    cumulativeFieldsMap.get(field).add(currentOpp.get(field));
                }

                for (String field : AWAL_SduConstants.OPP_FIELDS) {
                    Object basevalue;
                    Object currentValue;

                    currentValue = SchemaUtils.getFieldValueFromSObject(currentOpp, field);

                    if (currentValue == null || field == 'Id')
                        continue;

                    if (CUMULATIVE_FIELDS.contains(field) && field != 'TotalAdvance__c') {
                        amendedFields.add(field);
                        continue;
                    }

                    baseValue = SchemaUtils.getFieldValueFromSObject(baselineSnapshot, field);

                    if (baseValue == null || (currentValue != baseValue)) {
                        amendedFields.add(
                            AWAL_SduConstants.GROUPED_FIELDS_MAP.containsKey(field)
                                ? AWAL_SduConstants.GROUPED_FIELDS_MAP.get(field)
                                : field
                        );
                    }
                }

                for (String field : AWAL_SduConstants.OPP_FIELDS) {
                    Object currentValue = SchemaUtils.getFieldValueFromSObject(currentOpp, field);
                    if (currentValue != null) {
                        if (field.contains('.')) {
                            List<String> composedName = field.split('\\.');
                            String relationshipName = composedName[0];
                            String relationshipField = composedName[1];
                            SchemaUtils.populateRelated(
                                baselineSnapshot,
                                relationshipName,
                                new Map<String, Object>{ relationshipField => currentValue }
                            );
                        } else {
                            baselineSnapshot.put(field, currentValue);
                        }
                    }
                }

                Formula.recalculateFormulas(new List<Opportunity>{ baselineSnapshot });

                return new SnapshotWrapper(
                    baselineSnapshot,
                    amendedFields,
                    formatCumulativeText(cumulativeFieldsMap, currentOpp.CurrencyIsoCode)
                );
            }
        } catch (Exception ex) {
            ErrorLogger.logError(ex);
            return null;
        }
    }

    /**
     * Formats cumulative field values for display, including original, amendments, current, and total values.
     * @param cumulativeMap Map of field names to lists of amendment values
     * @param currencyIsoCode The currency ISO code for formatting
     * @return Map of field names to formatted cumulative value strings
     */
    private static Map<String, String> formatCumulativeText(
        Map<String, List<Object>> cumulativeMap,
        String currencyIsoCode
    ) {
        Map<String, String> result = new Map<String, String>();
        final Integer NUMBER_OF_OPPS = cumulativeMap.values()[0].size();
        for (String field : cumulativeMap.keySet()) {
            String fieldType = AWAL_SduConstants.OPP_CUMULATIVE_CURRENCY_FIELDS.contains(field)
                ? FormatUtils.FIELD_TYPE_CURRENCY
                : FormatUtils.FIELD_TYPE_NUMBER;
            List<String> lines = new List<String>();
            Decimal cumulativeValue = 0;
            for (Integer i = 0; i < NUMBER_OF_OPPS; i++) {
                Decimal currentValue = (Decimal) cumulativeMap.get(field)[i];
                cumulativeValue += currentValue != null ? currentValue : 0;
                String currentValueStr;
                if (AWAL_SduConstants.OPP_CUMULATIVE_CURRENCY_FIELDS.contains(field)) {
                    currentValueStr = currentValue != null
                        ? FormatUtils.formatValue(currentValue, fieldType, currencyIsoCode)
                        : ' - ';
                } else {
                    currentValueStr = currentValue != null
                        ? FormatUtils.formatValue(currentValue, fieldType, currencyIsoCode)
                        : ' - ';
                }
                if (i == 0) {
                    lines.add('• Original: ' + currentValueStr);
                } else if (i == NUMBER_OF_OPPS - 1) {
                    lines.add('• Current Opp: ' + currentValueStr);
                } else {
                    lines.add('• Amendment ' + i + ': ' + currentValueStr);
                }
            }
            lines.add('• Total: ' + FormatUtils.formatValue(cumulativeValue, fieldType, currencyIsoCode));
            result.put(field, String.join(lines, '<br/>'));
        }
        return result;
    }

    public class SnapshotWrapper {
        @AuraEnabled
        public Opportunity snapshot;
        @AuraEnabled
        public List<String> amendedFields;
        @AuraEnabled
        public Map<String, String> cumulativeMap;

        public SnapshotWrapper(Opportunity snapshot, List<String> amendedFields, Map<String, String> cumulativeMap) {
            this.snapshot = snapshot;
            this.amendedFields = amendedFields;
            this.cumulativeMap = cumulativeMap;
        }
    }

    public class FieldsWrapper {
        @AuraEnabled
        public List<String> sduFields;
        @AuraEnabled
        public List<String> oppFields;
        @AuraEnabled
        public List<String> groupedFields;
        @AuraEnabled
        public List<String> oppBooleanFields;
        @AuraEnabled
        public List<String> sduPicklists;

        public FieldsWrapper(
            List<String> sduFields,
            List<String> oppFields,
            List<String> groupedFields,
            List<String> oppBooleanFields,
            List<String> sduPicklists
        ) {
            this.sduFields = sduFields;
            this.oppFields = oppFields;
            this.groupedFields = groupedFields;
            this.oppBooleanFields = oppBooleanFields;
            this.sduPicklists = sduPicklists;
        }
    }
}