public with sharing class DocusignESignatureService {
    public class ESignatureAccountConfig {
        @AuraEnabled public String accountCode;
        @AuraEnabled public String accountDisplayName;
        @AuraEnabled public String environment;
        @AuraEnabled public String eSignatureAuthNamedCredential;
        @AuraEnabled public String eSignatureRestNamedCredential;
        @AuraEnabled public String eSignatureAccountId;
    }

    public class ApiResponse {
        @AuraEnabled public Boolean success;
        @AuraEnabled public Integer statusCode;
        @AuraEnabled public String message;
        @AuraEnabled public String requestPath;
        @AuraEnabled public String responseBody;
    }

    public class ESignatureAccountSummary {
        @AuraEnabled public String accountId;
        @AuraEnabled public String accountName;
        @AuraEnabled public String baseUri;
        @AuraEnabled public Boolean isDefault;
        @AuraEnabled public String rawJson;
    }

    public class TemplateSummary {
        @AuraEnabled public String templateId;
        @AuraEnabled public String name;
        @AuraEnabled public String description;
        @AuraEnabled public String shared;
        @AuraEnabled public String lastModified;
        @AuraEnabled public String rawJson;
    }

    public class EnvelopeSummary {
        @AuraEnabled public String envelopeId;
        @AuraEnabled public String emailSubject;
        @AuraEnabled public String status;
        @AuraEnabled public String createdDateTime;
        @AuraEnabled public String sentDateTime;
        @AuraEnabled public String completedDateTime;
        @AuraEnabled public String rawJson;
    }

    @AuraEnabled(cacheable=true)
    public static ESignatureAccountConfig getAccountConfig(String accountCode) {
        CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
        ESignatureAccountConfig config = new ESignatureAccountConfig();
        config.accountCode = String.isNotBlank(row.Account_Code__c) ? row.Account_Code__c : row.DeveloperName;
        config.accountDisplayName = String.isNotBlank(row.Account_Display_Name__c) ? row.Account_Display_Name__c : row.DeveloperName;
        config.environment = row.Environment_Code__c;
        config.eSignatureAuthNamedCredential = row.ESignature_Auth_Named_Credential__c;
        config.eSignatureRestNamedCredential = row.ESignature_Rest_Named_Credential__c;
        config.eSignatureAccountId = row.ESignature_Account_Id__c;
        return config;
    }

    @AuraEnabled(cacheable=false)
    public static ApiResponse probe(String accountCode, String relativePath) {
        CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
        String normalizedPath = normalizePath(relativePath);

        HttpRequest req = new HttpRequest();
        req.setEndpoint(buildEndpoint(normalizedPath, row.ESignature_Rest_Named_Credential__c));
        req.setMethod('GET');
        req.setTimeout(30000);

        HttpResponse res = new Http().send(req);
        ApiResponse response = new ApiResponse();
        response.success = res.getStatusCode() >= 200 && res.getStatusCode() < 300;
        response.statusCode = res.getStatusCode();
        response.message = response.success ? 'eSignature request succeeded.' : 'eSignature request failed.';
        response.requestPath = normalizedPath;
        response.responseBody = res.getBody();
        return response;
    }

    @AuraEnabled(cacheable=false)
    public static List<ESignatureAccountSummary> listAccounts(String accountCode) {
        ApiResponse response = getLoginInformation(accountCode);
        if (!response.success) {
            throw new AuraHandledException('eSignature API Error (HTTP ' + response.statusCode + '): ' + response.responseBody);
        }
        return parseAccountList(response.responseBody);
    }

    @AuraEnabled(cacheable=false)
    public static ApiResponse getLoginInformation(String accountCode) {
        return probe(accountCode, '/v2.1/login_information');
    }

    @AuraEnabled(cacheable=false)
    public static ApiResponse getUserInfo(String accountCode) {
        CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);

        HttpRequest req = new HttpRequest();
        req.setEndpoint(buildEndpoint('/oauth/userinfo', authNamedCredential(row)));
        req.setMethod('GET');
        req.setTimeout(30000);

        HttpResponse res = new Http().send(req);
        ApiResponse response = new ApiResponse();
        response.success = res.getStatusCode() >= 200 && res.getStatusCode() < 300;
        response.statusCode = res.getStatusCode();
        response.message = response.success ? 'eSignature user info request succeeded.' : 'eSignature user info request failed.';
        response.requestPath = '/oauth/userinfo';
        response.responseBody = res.getBody();
        return response;
    }

    @AuraEnabled(cacheable=false)
    public static ApiResponse getAccountInformation(String accountCode, String eSignatureAccountId) {
        CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
        String targetAccountId = requireESignatureAccountId(row, accountCode, eSignatureAccountId);
        return probe(accountCode, '/v2.1/accounts/' + targetAccountId);
    }

    @AuraEnabled(cacheable=false)
    public static List<TemplateSummary> listTemplates(String accountCode) {
        CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
        String targetAccountId = requireESignatureAccountId(row, accountCode, null);
        ApiResponse response = probe(accountCode, '/v2.1/accounts/' + targetAccountId + '/templates');
        if (!response.success) {
            throw new AuraHandledException('eSignature API Error (HTTP ' + response.statusCode + '): ' + response.responseBody);
        }
        return parseTemplateList(response.responseBody);
    }

    @AuraEnabled(cacheable=false)
    public static List<EnvelopeSummary> listEnvelopes(String accountCode, String fromDate) {
        CLM_Account_Setting__mdt row = requireAccountSetting(accountCode);
        String targetAccountId = requireESignatureAccountId(row, accountCode, null);
        String normalizedFromDate = String.isBlank(fromDate)
            ? DateTime.newInstanceGMT(Date.today().addDays(-30), Time.newInstance(0, 0, 0, 0)).formatGMT('yyyy-MM-dd')
            : fromDate.trim();
        ApiResponse response = probe(
            accountCode,
            '/v2.1/accounts/' + targetAccountId + '/envelopes?from_date=' + EncodingUtil.urlEncode(normalizedFromDate, 'UTF-8')
        );
        if (!response.success) {
            throw new AuraHandledException('eSignature API Error (HTTP ' + response.statusCode + '): ' + response.responseBody);
        }
        return parseEnvelopeList(response.responseBody);
    }

    @TestVisible
    private static List<ESignatureAccountSummary> parseAccountList(String body) {
        Object root = JSON.deserializeUntyped(body);
        List<Object> records = new List<Object>();
        if (root instanceof List<Object>) {
            records = (List<Object>) root;
        } else if (root instanceof Map<String, Object>) {
            Object loginAccounts = ((Map<String, Object>) root).get('loginAccounts');
            if (loginAccounts instanceof List<Object>) {
                records = (List<Object>) loginAccounts;
            }
            Object accounts = ((Map<String, Object>) root).get('accounts');
            if (records.isEmpty() && accounts instanceof List<Object>) {
                records = (List<Object>) accounts;
            }
        }

        List<ESignatureAccountSummary> summaries = new List<ESignatureAccountSummary>();
        for (Object record : records) {
            if (!(record instanceof Map<String, Object>)) {
                continue;
            }
            Map<String, Object> row = (Map<String, Object>) record;
            ESignatureAccountSummary summary = new ESignatureAccountSummary();
            summary.accountId = firstString(row, new List<String>{ 'accountId', 'account_id' });
            summary.accountName = firstString(row, new List<String>{ 'accountName', 'account_name', 'name' });
            summary.baseUri = firstString(row, new List<String>{ 'baseUri', 'base_uri', 'baseUrl', 'base_url' });
            summary.isDefault = parseBoolean(row.get('isDefault'));
            summary.rawJson = JSON.serialize(row);
            summaries.add(summary);
        }
        return summaries;
    }

    @TestVisible
    private static List<TemplateSummary> parseTemplateList(String body) {
        List<Object> records = extractCollection(body, new List<String>{ 'envelopeTemplates', 'templates' });
        List<TemplateSummary> summaries = new List<TemplateSummary>();
        for (Object record : records) {
            if (!(record instanceof Map<String, Object>)) {
                continue;
            }
            Map<String, Object> row = (Map<String, Object>) record;
            TemplateSummary summary = new TemplateSummary();
            summary.templateId = firstString(row, new List<String>{ 'templateId', 'template_id' });
            summary.name = firstString(row, new List<String>{ 'name' });
            summary.description = firstString(row, new List<String>{ 'description' });
            summary.shared = firstString(row, new List<String>{ 'shared' });
            summary.lastModified = firstString(row, new List<String>{ 'lastModified', 'last_modified', 'lastModifiedDateTime' });
            summary.rawJson = JSON.serialize(row);
            summaries.add(summary);
        }
        return summaries;
    }

    @TestVisible
    private static List<EnvelopeSummary> parseEnvelopeList(String body) {
        List<Object> records = extractCollection(body, new List<String>{ 'envelopes' });
        List<EnvelopeSummary> summaries = new List<EnvelopeSummary>();
        for (Object record : records) {
            if (!(record instanceof Map<String, Object>)) {
                continue;
            }
            Map<String, Object> row = (Map<String, Object>) record;
            EnvelopeSummary summary = new EnvelopeSummary();
            summary.envelopeId = firstString(row, new List<String>{ 'envelopeId', 'envelope_id' });
            summary.emailSubject = firstString(row, new List<String>{ 'emailSubject', 'email_subject' });
            summary.status = firstString(row, new List<String>{ 'status' });
            summary.createdDateTime = firstString(row, new List<String>{ 'createdDateTime', 'created_datetime' });
            summary.sentDateTime = firstString(row, new List<String>{ 'sentDateTime', 'sent_datetime' });
            summary.completedDateTime = firstString(row, new List<String>{ 'completedDateTime', 'completed_datetime' });
            summary.rawJson = JSON.serialize(row);
            summaries.add(summary);
        }
        return summaries;
    }

    @TestVisible
    private static String buildEndpoint(String relativePath, String namedCredential) {
        if (String.isBlank(namedCredential)) {
            throw new AuraHandledException('No eSignature named credential is configured for this account.');
        }
        return 'callout:' + namedCredential + normalizePath(relativePath);
    }

    private static String normalizePath(String relativePath) {
        if (String.isBlank(relativePath)) {
            throw new AuraHandledException('A relative path is required.');
        }
        return relativePath.startsWith('/') ? relativePath : '/' + relativePath;
    }

    private static CLM_Account_Setting__mdt requireAccountSetting(String accountCode) {
        String normalizedCode = String.isBlank(accountCode) ? null : accountCode.trim();
        List<CLM_Account_Setting__mdt> rows = new List<CLM_Account_Setting__mdt>();
        if (String.isNotBlank(normalizedCode)) {
            rows = [
                SELECT DeveloperName,
                       Account_Code__c,
                       Account_Display_Name__c,
                       Environment_Code__c,
                       ESignature_Auth_Named_Credential__c,
                       ESignature_Rest_Named_Credential__c,
                       ESignature_Account_Id__c,
                       Active__c
                  FROM CLM_Account_Setting__mdt
                 WHERE Active__c = true
                   AND DeveloperName = :normalizedCode
                 LIMIT 1
            ];
            if (rows.isEmpty()) {
                rows = [
                    SELECT DeveloperName,
                           Account_Code__c,
                           Account_Display_Name__c,
                           Environment_Code__c,
                           ESignature_Auth_Named_Credential__c,
                           ESignature_Rest_Named_Credential__c,
                           ESignature_Account_Id__c,
                           Active__c
                      FROM CLM_Account_Setting__mdt
                     WHERE Active__c = true
                       AND Account_Code__c = :normalizedCode
                     LIMIT 1
                ];
            }
        }

        if (rows.isEmpty()) {
            throw new AuraHandledException('No active CLM account setting was found for ' + accountCode + '.');
        }

        CLM_Account_Setting__mdt row = rows[0];
        if (String.isBlank(row.ESignature_Rest_Named_Credential__c)) {
            throw new AuraHandledException('No eSignature named credential is configured for ' + accountCode + '.');
        }
        return row;
    }

    private static String authNamedCredential(CLM_Account_Setting__mdt row) {
        return String.isNotBlank(row.ESignature_Auth_Named_Credential__c)
            ? row.ESignature_Auth_Named_Credential__c
            : row.ESignature_Rest_Named_Credential__c;
    }

    private static String requireESignatureAccountId(CLM_Account_Setting__mdt row, String accountCode, String eSignatureAccountId) {
        String targetAccountId = String.isNotBlank(eSignatureAccountId) ? eSignatureAccountId : row.ESignature_Account_Id__c;
        if (String.isBlank(targetAccountId)) {
            throw new AuraHandledException('No eSignature account id is configured for ' + accountCode + '.');
        }
        return targetAccountId;
    }

    private static List<Object> extractCollection(String body, List<String> keys) {
        Object root = JSON.deserializeUntyped(body);
        if (root instanceof List<Object>) {
            return (List<Object>) root;
        }
        if (root instanceof Map<String, Object>) {
            Map<String, Object> source = (Map<String, Object>) root;
            for (String key : keys) {
                Object records = source.get(key);
                if (records instanceof List<Object>) {
                    return (List<Object>) records;
                }
            }
        }
        return new List<Object>();
    }

    private static String firstString(Map<String, Object> source, List<String> keys) {
        for (String key : keys) {
            Object value = source.get(key);
            if (value != null) {
                String text = String.valueOf(value);
                if (String.isNotBlank(text)) {
                    return text;
                }
            }
        }
        return null;
    }

    private static Boolean parseBoolean(Object value) {
        if (value == null) {
            return false;
        }
        if (value instanceof Boolean) {
            return (Boolean) value;
        }
        return String.valueOf(value).toLowerCase() == 'true';
    }
}
