/**
 * @description Combines multiple Docusign templates into a single composite envelope
 *              using the dfsle Apex Toolkit (Docusign for Salesforce managed package).
 *              Recipients are resolved from Client_Case__c lookup fields.
 * @author Paul Huliganga
 * @date 2026-02-25
 */
global with sharing class DocusignCompositeEnvelopeBuilder {
    
    // ============================================================
    // CONFIGURATION: Update these constants if field/role names change
    // ============================================================
    
    // API names of the lookup fields on Client_Case__c that point to recipient records
    // These are the "Select Lookup Field" values from the Docusign template recipient config
    private static final String FIELD_SERVICE_COORDINATOR = 'Service_Coordinator__c';
    private static final String FIELD_DOCUSIGN_RECIPIENT  = 'Docusign_Recipient_1__c';
    
    // Role names must match EXACTLY what's configured in the Docusign templates
    private static final String ROLE_SERVICE_COORDINATOR  = 'Service Coordinator';
    private static final String ROLE_DOCUSIGN_RECIPIENT   = 'Docusign Recipient #1';
    
    @InvocableMethod(
        label='Send Composite Docusign Envelope' 
        description='Combines multiple Docusign templates into a single envelope using dfsle Apex Toolkit'
        category='Docusign'
    )
    public static List<DocusignEnvelopeResult> sendCompositeEnvelope(List<DocusignEnvelopeRequest> requests) {
        List<DocusignEnvelopeResult> results = new List<DocusignEnvelopeResult>();
        
        if (requests == null || requests.isEmpty()) {
            return buildErrorResult('No request provided');
        }
        
        DocusignEnvelopeRequest req = requests[0];
        DocusignEnvelopeResult result = new DocusignEnvelopeResult();
        
        try {
            // Validate request
            DocusignEnvelopeRequestHandler.validateRequest(req);
            
            // Create empty envelope linked to the source record
            dfsle.Envelope myEnvelope = dfsle.EnvelopeService.getEmptyEnvelope(
                new dfsle.Entity(req.recordId)
            );
            
            // Build document list from templates (deduplicated and sorted)
            List<String> sortedTemplateIds = new List<String>(new Set<String>(req.templateIds));
            sortedTemplateIds.sort();
            
            // Query template names for document labels (shows in Docusign Status)
            Map<String, String> templateNames = new Map<String, String>();
            for (dfsle__EnvelopeConfiguration__c config : [
                SELECT dfsle__DocuSignId__c, Name 
                FROM dfsle__EnvelopeConfiguration__c
                WHERE dfsle__DocuSignId__c IN :sortedTemplateIds
            ]) {
                templateNames.put(config.dfsle__DocuSignId__c, config.Name);
            }
            
            List<dfsle.Document> documents = new List<dfsle.Document>();
            List<String> docNames = new List<String>();
            for (String templateId : sortedTemplateIds) {
                String label = templateNames.containsKey(templateId) 
                    ? stripLanguageSuffix(templateNames.get(templateId))
                    : templateId;
                documents.add(
                    dfsle.Document.fromTemplate(
                        dfsle.UUID.parse(templateId),
                        label
                    )
                );
                docNames.add(label);
            }
            myEnvelope = myEnvelope.withDocuments(documents);
            
            // Set combined template names as the envelope document name
            // (shows in Docusign Status "Document Name" column)
            String combinedName = String.join(docNames, ', ');
            if (combinedName.length() > 255) {
                combinedName = combinedName.left(252) + '...';
            }
            // Use combined name as the first document label so it appears in Status
            if (!documents.isEmpty()) {
                documents[0] = dfsle.Document.fromTemplate(
                    dfsle.UUID.parse(sortedTemplateIds[0]),
                    combinedName
                );
                myEnvelope = myEnvelope.withDocuments(documents);
            }
            
            // Resolve recipients from Client_Case__c lookup fields
            List<dfsle.Recipient> recipients = resolveRecipients(req.recordId);
            myEnvelope = myEnvelope.withRecipients(recipients);
            
            // Set email subject if provided (truncated to 100 chars max)
            if (String.isNotBlank(req.emailSubject)) {
                String truncatedSubject = req.emailSubject.length() > 100 
                    ? req.emailSubject.left(97) + '...'
                    : req.emailSubject;
                myEnvelope = myEnvelope.withEmail(truncatedSubject, '');
            }
            
            // Send the envelope
            myEnvelope = dfsle.EnvelopeService.sendEnvelope(myEnvelope, true);
            
            // Success
            result.envelopeId = String.valueOf(myEnvelope.docuSignId);
            result.success = true;
            result.errorMessage = null;
            
            logResult(sortedTemplateIds.size(), result.envelopeId, 'Success (' + String.join(docNames, ', ') + ')', null);
            
        } catch (Exception e) {
            result.success = false;
            result.errorMessage = e.getMessage();
            result.envelopeId = null;
            
            logResult(
                req.templateIds != null ? req.templateIds.size() : 0,
                null, 'Error',
                e.getMessage() + '\n' + e.getStackTraceString()
            );
            
            if (e instanceof System.LimitException) {
                throw e;
            }
        }
        
        results.add(result);
        return results;
    }
    
    /**
     * @description Resolves recipients from Client_Case__c lookup fields.
     *              Queries the case record and related contacts to get name/email.
     * @param recordId The Client_Case__c record ID
     * @return List of dfsle.Recipient objects with role mappings
     */
    private static List<dfsle.Recipient> resolveRecipients(String recordId) {
        // Query the Client_Case__c record with recipient lookup fields
        // NOTE: Adjust field API names if they differ in your org
        String query = 'SELECT Id, '
            + FIELD_SERVICE_COORDINATOR + ', '
            + FIELD_DOCUSIGN_RECIPIENT
            + ' FROM Client_Case__c WHERE Id = :recordId LIMIT 1';
        
        Client_Case__c caseRecord = Database.query(query);
        
        List<dfsle.Recipient> recipients = new List<dfsle.Recipient>();
        Integer routingOrder = 1;
        
        // Recipient 1: Service Coordinator
        Id serviceCoordinatorId = (Id) caseRecord.get(FIELD_SERVICE_COORDINATOR);
        if (serviceCoordinatorId != null) {
            recipients.add(buildRecipient(serviceCoordinatorId, ROLE_SERVICE_COORDINATOR, routingOrder++, recordId));
        }
        
        // Recipient 2: Docusign Recipient #1
        Id docusignRecipientId = (Id) caseRecord.get(FIELD_DOCUSIGN_RECIPIENT);
        if (docusignRecipientId != null) {
            recipients.add(buildRecipient(docusignRecipientId, ROLE_DOCUSIGN_RECIPIENT, routingOrder++, recordId));
        }
        
        if (recipients.isEmpty()) {
            throw new IllegalArgumentException('No recipients found on the Client Case record. '
                + 'Please ensure Service Coordinator and Docusign Recipient #1 are populated.');
        }
        
        return recipients;
    }
    
    /**
     * @description Builds a dfsle.Recipient from a Contact/User lookup ID
     * @param recipientId The Contact or User record ID
     * @param roleName The Docusign template role name
     * @param routingOrder Signing order
     * @param sourceRecordId The source Client_Case__c record ID
     * @return dfsle.Recipient configured for the role
     */
    private static dfsle.Recipient buildRecipient(Id recipientId, String roleName, Integer routingOrder, String sourceRecordId) {
        // Determine if this is a Contact or User
        String objectType = recipientId.getSObjectType().getDescribe().getName();
        
        String recipientName;
        String recipientEmail;
        
        if (objectType == 'Contact') {
            Contact c = [SELECT Id, Name, Email FROM Contact WHERE Id = :recipientId LIMIT 1];
            recipientName = c.Name;
            recipientEmail = c.Email;
        } else if (objectType == 'User') {
            User u = [SELECT Id, Name, Email FROM User WHERE Id = :recipientId LIMIT 1];
            recipientName = u.Name;
            recipientEmail = u.Email;
        } else {
            throw new IllegalArgumentException('Unsupported recipient type: ' + objectType 
                + '. Expected Contact or User.');
        }
        
        if (String.isBlank(recipientEmail)) {
            throw new IllegalArgumentException('No email found for ' + roleName + ' (' + recipientName + '). '
                + 'Please ensure the recipient has a valid email address.');
        }
        
        return dfsle.Recipient.fromSource(
            recipientName,
            recipientEmail,
            null,                                    // phone (optional)
            roleName,                                // must match template role exactly
            new dfsle.Entity(sourceRecordId)          // source record for merge fields
        );
    }
    
    private static void logResult(Integer templateCount, String envelopeId, String status, String errorMessage) {
        System.debug(LoggingLevel.INFO, '=== Docusign Composite Envelope ===');
        System.debug(LoggingLevel.INFO, 'Templates: ' + templateCount);
        System.debug(LoggingLevel.INFO, 'Envelope ID: ' + envelopeId);
        System.debug(LoggingLevel.INFO, 'Status: ' + status);
        if (String.isNotBlank(errorMessage)) {
            System.debug(LoggingLevel.ERROR, 'Error: ' + errorMessage);
        }
    }
    
    /**
     * @description Strips language suffixes like " - English" or " - Spanish" from template names
     * @param name Template name
     * @return Cleaned template name
     */
    @TestVisible
    private static String stripLanguageSuffix(String name) {
        if (String.isBlank(name)) return name;
        // Remove common language suffixes (case-insensitive)
        String cleaned = name;
        for (String suffix : new List<String>{
            ' - English', ' - Spanish', ' - French',
            ' - Anglais', ' - Espagnol', ' - Français'
        }) {
            if (cleaned.endsWithIgnoreCase(suffix)) {
                cleaned = cleaned.left(cleaned.length() - suffix.length());
                break;
            }
        }
        return cleaned.trim();
    }
    
    private static List<DocusignEnvelopeResult> buildErrorResult(String errorMessage) {
        DocusignEnvelopeResult result = new DocusignEnvelopeResult();
        result.success = false;
        result.errorMessage = errorMessage;
        result.envelopeId = null;
        return new List<DocusignEnvelopeResult>{ result };
    }
}
