/***********************************************************************************************************
 * Name         : ErrorLogger
 * Purpose      : Exception Logger
 **********************************************************************************************************/

public class ErrorLogger {
    /**
     * Logs an Apex exception by creating and inserting an Apex_Error_Log__c record.
     * Silently catches any errors that occur during logging and logs them via System.debug.
     *
     * @param ex The Exception to log
     */
    public static void logError(Exception ex) {
        try {
            Apex_Error_Log__c apexErrorLog = getApexError(ex);
            insert apexErrorLog;
        } catch (Exception e) {
            System.debug('Failed to INSERT the [Apex Error Log] record. ' + 'Error: ' + e.getMessage());
        }
    }

    /**
     * Logs an Apex exception asynchronously (useful for @wire methods that don't allow DMLs)
     *
     * @param ex The Exception to log.
     */
    public static void logErrorFuture(Exception ex) {
        Apex_Error_Log__c apexErrorLog = getApexError(ex);
        String jsonApexErrorLog = JSON.serialize(apexErrorLog);
        logErrorFutureDML(jsonApexErrorLog);
    }

    /**
     * Performs the DML for the logErrorFuture method.
     *
     * @param jsonApexErrorLog The serialized error log.
     */
    @future
    private static void logErrorFutureDML(String jsonApexErrorLog) {
        Apex_Error_Log__c apexErrorLog = (Apex_Error_Log__c) JSON.deserialize(
            jsonApexErrorLog,
            Apex_Error_Log__c.class
        );
        insert apexErrorLog;
    }

    /**
     * Converts an Apex Exception into an Apex_Error_Log__c record populated with exception details.
     * Extracts error type, message, cause, line number, stack trace, class name, and method name.
     *
     * @param ex The Exception to convert
     * @return Apex_Error_Log__c The error log record (not inserted)
     */
    public static Apex_Error_Log__c getApexError(Exception ex) {
        Apex_Error_Log__c apexErrorLog = new Apex_Error_Log__c(
            Error_Type__c = String.valueOf(ex.getTypeName()),
            Error_Message__c = String.valueOf(ex.getMessage()),
            Error_Cause__c = String.valueOf(ex.getCause()),
            Error_Line__c = Integer.valueOf(ex.getLineNumber()),
            Error_Stack_Trace__c = String.valueOf(ex.getStackTraceString()),
            Error_Class_Name__c = ErrorLogger.getClassName(ex.getStackTraceString()),
            Error_Method_Name__c = ErrorLogger.getMethodName(ex.getStackTraceString())
        );

        return apexErrorLog;
    }

    /**
     * Logs a list of database errors by creating and inserting Apex_Error_Log__c records.
     * Associates errors with the calling class and method for better error tracking.
     *
     * @param dbErrors List of Database.Error objects from a failed database operation
     * @param errorClassName The class name where the error occurred
     * @param errorMethodName The method name where the error occurred
     * @param errorCause Additional context about what caused the error
     * @return List<Apex_Error_Log__c> The created and inserted error log records
     */
    public static List<Apex_Error_Log__c> logDbErrors(
        List<Database.Error> dbErrors,
        String errorClassName,
        String errorMethodName,
        String errorCause
    ) {
        List<Apex_Error_Log__c> apexErrorLogList = new List<Apex_Error_Log__c>();

        for (Database.Error error : dbErrors) {
            Apex_Error_Log__c apexErrorLog = new Apex_Error_Log__c(
                Error_Type__c = 'DB Error',
                Error_Message__c = error.getMessage(),
                Error_Cause__c = errorCause,
                Error_Class_Name__c = errorClassName,
                Error_Method_Name__c = errorMethodName
            );

            apexErrorLogList.add(apexErrorLog);
        }
        if (!apexErrorLogList.isEmpty()) {
            insert apexErrorLogList;
        }

        return apexErrorLogList;
    }

    /**
     * Formats Database.Error records as user-facing messages that preserve the status code,
     * message, and field names.
     *
     * @param dbErrors List of Database.Error objects from a failed database operation
     * @return List<String> The formatted error messages
     */
    public static List<String> getDbErrorMessages(List<Database.Error> dbErrors) {
        List<String> errorMessages = new List<String>();
        for (Database.Error error : dbErrors) {
            String errorMessage = String.valueOf(error.getStatusCode()) + ', ' + error.getMessage();
            List<String> fields = error.getFields();
            if (!fields.isEmpty()) {
                errorMessage += ': [' + String.join(fields, ',') + ']';
            }
            errorMessages.add(errorMessage);
        }
        return errorMessages;
    }

    /**
     * Converts failed SaveResult records into Apex_Error_Log__c records and inserts them.
     * Only processes results that have errors; skips successful operations.
     *
     * @param resultList List of Database.SaveResult objects from a DML operation
     * @param errorClassName The class name where the DML operation occurred
     * @param errorMethodName The method name where the DML operation occurred
     * @param errorCause Additional context about what caused the error
     * @return List<Apex_Error_Log__c> The created and inserted error log records
     */
    public static List<Apex_Error_Log__c> getApexError(
        List<Database.SaveResult> resultList,
        String errorClassName,
        String errorMethodName,
        String errorCause
    ) {
        List<Apex_Error_Log__c> apexErrorLogList = new List<Apex_Error_Log__c>();

        for (Database.SaveResult result : resultList) {
            if (!result.isSuccess()) {
                for (Database.Error err : result.getErrors()) {
                    System.debug(LoggingLevel.ERROR, '*** getApexError upsert: ' + err);

                    Apex_Error_Log__c apexErrorLog = new Apex_Error_Log__c(
                        Error_Type__c = 'DML Error',
                        Error_Message__c = String.valueOf(err.getMessage()),
                        Error_Record_Id__c = result.getId(),
                        Error_Cause__c = errorCause,
                        Error_Class_Name__c = errorClassName,
                        Error_Method_Name__c = errorMethodName
                    );

                    apexErrorLogList.add(apexErrorLog);
                }
            }
        }

        if (!apexErrorLogList.isEmpty()) {
            insert apexErrorLogList;
        }

        return apexErrorLogList;
    }

    /**
     * Converts failed DeleteResult records into Apex_Error_Log__c records and inserts them.
     * Only processes results that have errors; skips successful delete operations.
     *
     * @param resultList List of Database.DeleteResult objects from a delete operation
     * @param errorClassName The class name where the delete operation occurred
     * @param errorMethodName The method name where the delete operation occurred
     * @param errorCause Additional context about what caused the error
     * @return List<Apex_Error_Log__c> The created and inserted error log records
     */
    public static List<Apex_Error_Log__c> getApexError(
        List<Database.DeleteResult> resultList,
        String errorClassName,
        String errorMethodName,
        String errorCause
    ) {
        List<Apex_Error_Log__c> apexErrorLogList = new List<Apex_Error_Log__c>();

        for (Database.DeleteResult result : resultList) {
            if (!result.isSuccess()) {
                for (Database.Error err : result.getErrors()) {
                    System.debug(LoggingLevel.ERROR, '*** getApexError upsert: ' + err);

                    Apex_Error_Log__c apexErrorLog = new Apex_Error_Log__c(
                        Error_Type__c = 'DML Error',
                        Error_Message__c = String.valueOf(err.getMessage()),
                        Error_Record_Id__c = result.getId(),
                        Error_Cause__c = errorCause,
                        Error_Class_Name__c = errorClassName,
                        Error_Method_Name__c = errorMethodName
                    );

                    apexErrorLogList.add(apexErrorLog);
                }
            }
        }

        if (!apexErrorLogList.isEmpty()) {
            insert apexErrorLogList;
        }

        return apexErrorLogList;
    }

    /**
     * Converts failed UpsertResult records into Apex_Error_Log__c records and inserts them.
     * Only processes results that have errors; skips successful upsert operations.
     *
     * @param resultList List of Database.UpsertResult objects from an upsert operation
     * @param errorClassName The class name where the upsert operation occurred
     * @param errorMethodName The method name where the upsert operation occurred
     * @param errorCause Additional context about what caused the error
     * @return List<Apex_Error_Log__c> The created and inserted error log records
     */
    public static List<Apex_Error_Log__c> getApexError(
        List<Database.UpsertResult> resultList,
        String errorClassName,
        String errorMethodName,
        String errorCause
    ) {
        List<Apex_Error_Log__c> apexErrorLogList = new List<Apex_Error_Log__c>();

        for (Database.UpsertResult result : resultList) {
            if (!result.isSuccess()) {
                for (Database.Error err : result.getErrors()) {
                    System.debug(LoggingLevel.ERROR, '*** getApexError upsert: ' + err);

                    Apex_Error_Log__c apexErrorLog = new Apex_Error_Log__c(
                        Error_Type__c = 'DML Error',
                        Error_Message__c = String.valueOf(err.getMessage()),
                        Error_Record_Id__c = result.getId(),
                        Error_Cause__c = errorCause,
                        Error_Class_Name__c = errorClassName,
                        Error_Method_Name__c = errorMethodName
                    );

                    apexErrorLogList.add(apexErrorLog);
                }
            }
        }

        if (!apexErrorLogList.isEmpty()) {
            insert apexErrorLogList;
        }

        return apexErrorLogList;
    }

    /**
     * Extracts the class name from a stack trace string.
     * Parses the format "Class.ClassName.methodName: line X" to return "ClassName".
     *
     * @param stackTrace The stack trace string to parse
     * @return String The extracted class name, or null if the stack trace is blank or cannot be parsed
     */
    @TestVisible
    private static String getClassName(String stackTrace) {
        if (String.isBlank(stackTrace)) {
            return null;
        }
        if (stackTrace.startsWith('Class.')) {
            stackTrace = stackTrace.substringAfter('Class.');
        }
        return stackTrace.substringBefore(':').substringBeforeLast('.');
    }

    /**
     * Extracts the method name from a stack trace string.
     * Parses the format "Class.ClassName.methodName: line X" to return "methodName".
     *
     * @param stackTrace The stack trace string to parse
     * @return String The extracted method name, or null if the stack trace is blank
     */
    @TestVisible
    private static String getMethodName(String stackTrace) {
        if (String.isBlank(stackTrace)) {
            return null;
        }
        return stackTrace.substringBefore(':').substringAfterLast('.');
    }
}