/**
 * @description Test class for DocusignAPIService
 * @author Paul Huliganga
 * @date 2026-02-23
 */
@isTest
private class DocusignAPIServiceTest {
    
    /**
     * @description Mock HTTP callout
     */
    private class DocusignMock implements HttpCalloutMock {
        private Integer statusCode;
        private String responseBody;
        private Integer callCount = 0;
        
        public DocusignMock(Integer statusCode, String responseBody) {
            this.statusCode = statusCode;
            this.responseBody = responseBody;
        }
        
        public HTTPResponse respond(HTTPRequest req) {
            callCount++;
            HttpResponse res = new HttpResponse();
            res.setStatusCode(this.statusCode);
            res.setBody(this.responseBody);
            res.setStatus(this.statusCode == 201 ? 'Created' : 'Error');
            return res;
        }
    }
    
    /**
     * @description Mock that fails first time, succeeds second (for retry testing)
     */
    private class RetryMock implements HttpCalloutMock {
        private Integer callCount = 0;
        
        public HTTPResponse respond(HTTPRequest req) {
            callCount++;
            HttpResponse res = new HttpResponse();
            
            if (callCount == 1) {
                // First call fails with 500
                res.setStatusCode(500);
                res.setBody('{"errorCode":"INTERNAL_ERROR","message":"Server error"}');
                res.setStatus('Internal Server Error');
            } else {
                // Second call succeeds
                res.setStatusCode(201);
                res.setBody('{"envelopeId":"envelope-retry-success","status":"sent"}');
                res.setStatus('Created');
            }
            
            return res;
        }
    }
    
    /**
     * @description Test successful envelope creation
     */
    @isTest
    static void testSuccessfulEnvelopeCreation() {
        // Arrange
        String envelopeId = 'envelope-test-12345';
        String mockResponse = '{"envelopeId":"' + envelopeId + '","status":"sent"}';
        Test.setMock(HttpCalloutMock.class, new DocusignMock(201, mockResponse));
        
        DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token');
        DocusignCredentials creds = DocusignCredentials.getInstance();
        
        String envelopeJSON = '{"status":"sent","compositeTemplates":[]}';
        
        // Act
        Test.startTest();
        String resultEnvelopeId = DocusignAPIService.createCompositeEnvelope(envelopeJSON, creds);
        Test.stopTest();
        
        // Assert
        System.assertEquals(envelopeId, resultEnvelopeId, 'Should return envelope ID');
    }
    
    /**
     * @description Test parseEnvelopeId method
     */
    @isTest
    static void testParseEnvelopeId() {
        // Arrange
        HttpResponse res = new HttpResponse();
        res.setStatusCode(201);
        res.setBody('{"envelopeId":"parsed-envelope-123","uri":"/envelopes/parsed-envelope-123"}');
        
        // Act
        Test.startTest();
        String envelopeId = DocusignAPIService.parseEnvelopeId(res);
        Test.stopTest();
        
        // Assert
        System.assertEquals('parsed-envelope-123', envelopeId, 'Should parse envelope ID correctly');
    }
    
    /**
     * @description Test parseEnvelopeId with missing ID
     */
    @isTest
    static void testParseEnvelopeIdMissing() {
        // Arrange
        HttpResponse res = new HttpResponse();
        res.setStatusCode(201);
        res.setBody('{"status":"sent"}'); // No envelopeId field
        
        // Act & Assert
        Test.startTest();
        try {
            DocusignAPIService.parseEnvelopeId(res);
            System.assert(false, 'Should have thrown exception');
        } catch (DocusignAPIService.CalloutException e) {
            System.assert(e.getMessage().contains('Envelope ID not found'), 'Should mention missing ID');
        }
        Test.stopTest();
    }
    
    /**
     * @description Test handleAPIError for 400 Bad Request
     */
    @isTest
    static void testHandleAPIError400() {
        // Arrange
        HttpResponse res = new HttpResponse();
        res.setStatusCode(400);
        res.setBody('{"errorCode":"INVALID_REQUEST_PARAMETER","message":"Invalid template ID"}');
        
        // Act & Assert
        Test.startTest();
        try {
            DocusignAPIService.handleAPIError(res);
            System.assert(false, 'Should have thrown exception');
        } catch (DocusignAPIService.CalloutException e) {
            System.assert(e.getMessage().contains('400'), 'Should include status code');
            System.assert(e.getMessage().contains('Invalid template'), 'Should include error message');
            System.assert(e.getMessage().contains('template IDs'), 'Should include guidance');
        }
        Test.stopTest();
    }
    
    /**
     * @description Test handleAPIError for 401 Unauthorized
     */
    @isTest
    static void testHandleAPIError401() {
        // Arrange
        HttpResponse res = new HttpResponse();
        res.setStatusCode(401);
        res.setBody('{"errorCode":"USER_AUTHENTICATION_FAILED","message":"Invalid access token"}');
        
        // Act & Assert
        Test.startTest();
        try {
            DocusignAPIService.handleAPIError(res);
            System.assert(false, 'Should have thrown exception');
        } catch (DocusignAPIService.CalloutException e) {
            System.assert(e.getMessage().contains('401'), 'Should include status code');
            System.assert(e.getMessage().contains('Authentication'), 'Should mention authentication');
        }
        Test.stopTest();
    }
    
    /**
     * @description Test handleAPIError for 403 Forbidden
     */
    @isTest
    static void testHandleAPIError403() {
        // Arrange
        HttpResponse res = new HttpResponse();
        res.setStatusCode(403);
        res.setBody('{"errorCode":"USER_LACKS_PERMISSIONS","message":"User cannot send envelopes"}');
        
        // Act & Assert
        Test.startTest();
        try {
            DocusignAPIService.handleAPIError(res);
            System.assert(false, 'Should have thrown exception');
        } catch (DocusignAPIService.CalloutException e) {
            System.assert(e.getMessage().contains('403'), 'Should include status code');
            System.assert(e.getMessage().contains('permission'), 'Should mention permission');
        }
        Test.stopTest();
    }
    
    /**
     * @description Test handleAPIError for 404 Not Found
     */
    @isTest
    static void testHandleAPIError404() {
        // Arrange
        HttpResponse res = new HttpResponse();
        res.setStatusCode(404);
        res.setBody('{"errorCode":"RESOURCE_NOT_FOUND","message":"Template not found"}');
        
        // Act & Assert
        Test.startTest();
        try {
            DocusignAPIService.handleAPIError(res);
            System.assert(false, 'Should have thrown exception');
        } catch (DocusignAPIService.CalloutException e) {
            System.assert(e.getMessage().contains('404'), 'Should include status code');
            System.assert(e.getMessage().contains('not found'), 'Should mention template not found');
        }
        Test.stopTest();
    }
    
    /**
     * @description Test handleAPIError for 429 Rate Limit
     */
    @isTest
    static void testHandleAPIError429() {
        // Arrange
        HttpResponse res = new HttpResponse();
        res.setStatusCode(429);
        res.setBody('{"errorCode":"HOURLY_APIINVOCATION_LIMIT_EXCEEDED","message":"Rate limit exceeded"}');
        
        // Act & Assert
        Test.startTest();
        try {
            DocusignAPIService.handleAPIError(res);
            System.assert(false, 'Should have thrown exception');
        } catch (DocusignAPIService.CalloutException e) {
            System.assert(e.getMessage().contains('429'), 'Should include status code');
            System.assert(e.getMessage().contains('rate limit'), 'Should mention rate limit');
        }
        Test.stopTest();
    }
    
    /**
     * @description Test handleAPIError for 500 Server Error
     */
    @isTest
    static void testHandleAPIError500() {
        // Arrange
        HttpResponse res = new HttpResponse();
        res.setStatusCode(500);
        res.setBody('{"errorCode":"INTERNAL_ERROR","message":"Server error"}');
        
        // Act & Assert
        Test.startTest();
        try {
            DocusignAPIService.handleAPIError(res);
            System.assert(false, 'Should have thrown exception');
        } catch (DocusignAPIService.CalloutException e) {
            System.assert(e.getMessage().contains('500'), 'Should include status code');
            System.assert(e.getMessage().contains('server error'), 'Should mention server error');
        }
        Test.stopTest();
    }
    
    /**
     * @description Test retry logic with transient failure
     */
    @isTest
    static void testRetryLogic() {
        // Arrange
        Test.setMock(HttpCalloutMock.class, new RetryMock());
        
        DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token');
        DocusignCredentials creds = DocusignCredentials.getInstance();
        
        String envelopeJSON = '{"status":"sent","compositeTemplates":[]}';
        
        // Act
        Test.startTest();
        String envelopeId = DocusignAPIService.createCompositeEnvelope(envelopeJSON, creds);
        Test.stopTest();
        
        // Assert
        System.assertEquals('envelope-retry-success', envelopeId, 'Should succeed after retry');
    }
    
    /**
     * @description Test buildCreateEnvelopeRequest
     */
    @isTest
    static void testBuildCreateEnvelopeRequest() {
        // Arrange
        DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token');
        DocusignCredentials creds = DocusignCredentials.getInstance();
        
        String envelopeJSON = '{"status":"sent"}';
        
        // Act
        Test.startTest();
        HttpRequest req = DocusignAPIService.buildCreateEnvelopeRequest(envelopeJSON, creds);
        Test.stopTest();
        
        // Assert
        System.assertEquals('POST', req.getMethod(), 'Should use POST method');
        System.assert(req.getEndpoint().contains('/envelopes'), 'Should have envelopes endpoint');
        System.assert(req.getHeader('Authorization').contains('Bearer'), 'Should have Bearer token');
        System.assertEquals('application/json', req.getHeader('Content-Type'), 'Should set JSON content type');
        System.assertEquals(envelopeJSON, req.getBody(), 'Should set body');
    }
    
    /**
     * @description Test executeWithRetry with non-retryable error
     */
    @isTest
    static void testExecuteWithRetryNonRetryable() {
        // Arrange
        String mockResponse = '{"errorCode":"INVALID_REQUEST_PARAMETER","message":"Bad request"}';
        Test.setMock(HttpCalloutMock.class, new DocusignMock(400, mockResponse));
        
        DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token');
        DocusignCredentials creds = DocusignCredentials.getInstance();
        
        HttpRequest req = DocusignAPIService.buildCreateEnvelopeRequest('{}', creds);
        
        // Act
        Test.startTest();
        HttpResponse res = DocusignAPIService.executeWithRetry(req, 2);
        Test.stopTest();
        
        // Assert
        System.assertEquals(400, res.getStatusCode(), 'Should return 400 without retry');
    }
}
