/**
 * @description Manages Docusign API credentials and access tokens
 * @author Paul Huliganga
 * @date 2026-02-23
 */
public with sharing class DocusignCredentials {
    
    private String baseUrl;
    private String accountId;
    private String accessToken;
    private DateTime tokenExpiry;
    
    // Singleton instance
    private static DocusignCredentials instance;
    
    // Flag to skip loadCredentials for setTestCredentials
    private static Boolean skipLoadForNextInstance = false;
    
    /**
     * @description Private constructor (singleton pattern)
     */
    private DocusignCredentials() {
        // Only load credentials if not explicitly skipped (for setTestCredentials)
        if (!skipLoadForNextInstance) {
            loadCredentials();
        }
        skipLoadForNextInstance = false;  // Reset flag
    }
    
    /**
     * @description Gets singleton instance of credentials
     * @return DocusignCredentials instance
     */
    public static DocusignCredentials getInstance() {
        if (instance == null) {
            instance = new DocusignCredentials();
        }
        
        // Refresh token if expired
        if (instance.isTokenExpired()) {
            instance.refreshAccessToken();
        }
        
        return instance;
    }
    
    /**
     * @description Loads credentials from Named Credential or Custom Settings
     */
    private void loadCredentials() {
        // Option 1: Using Named Credential (preferred)
        // When using Named Credential, the platform handles authentication automatically
        // We just need the account ID and base URL
        
        try {
            // Try to load from Custom Settings first
            Docusign_Configuration__c config = Docusign_Configuration__c.getOrgDefaults();
            
            if (config != null && String.isNotBlank(config.Account_Id__c)) {
                this.accountId = config.Account_Id__c;
                this.baseUrl = String.isNotBlank(config.Base_URL__c) 
                    ? config.Base_URL__c 
                    : 'callout:DocusignAPI'; // Default to Named Credential
                
                // If using Named Credential, token is managed by platform
                if (this.baseUrl.startsWith('callout:')) {
                    this.accessToken = 'MANAGED_BY_NAMED_CREDENTIAL';
                    this.tokenExpiry = DateTime.now().addHours(1);
                } else {
                    // Manual token management would go here
                    // For now, throw exception - must configure Named Credential
                    throw new CredentialException('Manual token management not implemented. Please use Named Credential.');
                }
            } else {
                throw new CredentialException('Docusign credentials not configured. Please set up Custom Settings: Docusign_Configuration__c');
            }
        } catch (Exception e) {
            throw new CredentialException('Failed to load Docusign credentials: ' + e.getMessage());
        }
    }
    
    /**
     * @description Checks if access token is expired
     * @return True if token is expired or about to expire (within 5 minutes)
     */
    private Boolean isTokenExpired() {
        if (this.tokenExpiry == null) {
            return true;
        }
        
        // Refresh 5 minutes before expiry to avoid edge cases
        DateTime threshold = DateTime.now().addMinutes(5);
        return this.tokenExpiry < threshold;
    }
    
    /**
     * @description Refreshes access token (JWT or OAuth2)
     * Note: In production with Named Credential, this is handled automatically by Salesforce
     */
    private void refreshAccessToken() {
        // If using Named Credential, no manual refresh needed
        if (this.baseUrl.startsWith('callout:')) {
            this.tokenExpiry = DateTime.now().addHours(1);
            return;
        }
        
        // Manual JWT token refresh would go here
        // This is a placeholder for future enhancement
        throw new CredentialException('Manual token refresh not implemented. Use Named Credential for automatic token management.');
    }
    
    /**
     * @description Gets access token
     * @return Access token string
     */
    public String getAccessToken() {
        if (isTokenExpired()) {
            refreshAccessToken();
        }
        return this.accessToken;
    }
    
    /**
     * @description Gets Docusign account ID
     * @return Account ID (GUID)
     */
    public String getAccountId() {
        return this.accountId;
    }
    
    /**
     * @description Gets base URL for Docusign API
     * @return Base URL (either Named Credential callout or full URL)
     */
    public String getBaseUrl() {
        return this.baseUrl;
    }
    
    /**
     * @description Validates that credentials are properly configured
     * @return True if valid
     * @throws CredentialException if invalid
     */
    public Boolean validate() {
        if (String.isBlank(this.accountId)) {
            throw new CredentialException('Docusign Account ID is not configured');
        }
        
        if (String.isBlank(this.baseUrl)) {
            throw new CredentialException('Docusign Base URL is not configured');
        }
        
        if (String.isBlank(this.accessToken)) {
            throw new CredentialException('Docusign Access Token is not available');
        }
        
        return true;
    }
    
    /**
     * @description Resets singleton instance (for testing)
     */
    @TestVisible
    private static void resetInstance() {
        instance = null;
    }
    
    /**
     * @description Sets credentials manually (for testing)
     */
    @TestVisible
    private static void setTestCredentials(String testAccountId, String testBaseUrl, String testAccessToken) {
        // Set flag to skip loadCredentials for this instance
        skipLoadForNextInstance = true;
        instance = new DocusignCredentials();
        // Override with specific test values
        instance.accountId = testAccountId;
        instance.baseUrl = testBaseUrl;
        instance.accessToken = testAccessToken;
        instance.tokenExpiry = DateTime.now().addHours(1);
    }
    
    /**
     * @description Custom exception for credential errors
     */
    public class CredentialException extends Exception {}
}
