using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace MarketAlly.AIPlugin.Analysis.Infrastructure { /// /// Enhanced error handling utilities for analysis operations /// public static class ErrorHandling { /// /// Executes an operation with retry logic and comprehensive error handling /// public static async Task ExecuteWithRetryAsync( Func> operation, int maxRetries = 3, TimeSpan? delay = null, ILogger? logger = null, CancellationToken cancellationToken = default, [CallerMemberName] string callerName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { var actualDelay = delay ?? TimeSpan.FromSeconds(1); var exceptions = new List(); var stopwatch = Stopwatch.StartNew(); for (int attempt = 0; attempt <= maxRetries; attempt++) { try { cancellationToken.ThrowIfCancellationRequested(); logger?.LogDebug("Executing operation {OperationName}, attempt {Attempt}/{MaxRetries}", callerName, attempt + 1, maxRetries + 1); var result = await operation(); if (attempt > 0) { logger?.LogInformation("Operation {OperationName} succeeded after {Attempts} attempts in {Duration}ms", callerName, attempt + 1, stopwatch.ElapsedMilliseconds); } return result; } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { logger?.LogWarning("Operation {OperationName} was cancelled after {Attempts} attempts", callerName, attempt + 1); throw; } catch (Exception ex) { exceptions.Add(ex); logger?.LogWarning(ex, "Operation {OperationName} failed on attempt {Attempt}/{MaxRetries} at {Location}", callerName, attempt + 1, maxRetries + 1, $"{callerFilePath}:{callerLineNumber}"); if (attempt == maxRetries) { logger?.LogError("Operation {OperationName} failed after {Attempts} attempts in {Duration}ms", callerName, attempt + 1, stopwatch.ElapsedMilliseconds); throw new AggregateException($"Operation {callerName} failed after {maxRetries + 1} attempts", exceptions); } if (ShouldRetry(ex)) { var nextDelay = CalculateDelay(actualDelay, attempt); logger?.LogDebug("Retrying operation {OperationName} in {Delay}ms", callerName, nextDelay.TotalMilliseconds); await Task.Delay(nextDelay, cancellationToken); } else { logger?.LogError(ex, "Operation {OperationName} failed with non-retryable exception", callerName); throw; } } } throw new InvalidOperationException("This should never be reached"); } /// /// Executes an operation with comprehensive error handling (non-generic version) /// public static async Task ExecuteWithRetryAsync( Func operation, int maxRetries = 3, TimeSpan? delay = null, ILogger? logger = null, CancellationToken cancellationToken = default, [CallerMemberName] string callerName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { await ExecuteWithRetryAsync(async () => { await operation(); return true; // Dummy return value }, maxRetries, delay, logger, cancellationToken, callerName, callerFilePath, callerLineNumber); } /// /// Safely executes an operation and returns a result with error information /// public static async Task> SafeExecuteAsync( Func> operation, ILogger? logger = null, [CallerMemberName] string callerName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { var stopwatch = Stopwatch.StartNew(); try { logger?.LogDebug("Starting safe execution of {OperationName}", callerName); var result = await operation(); logger?.LogDebug("Safe execution of {OperationName} completed successfully in {Duration}ms", callerName, stopwatch.ElapsedMilliseconds); return OperationResult.Success(result, stopwatch.Elapsed); } catch (Exception ex) { logger?.LogError(ex, "Safe execution of {OperationName} failed at {Location} after {Duration}ms", callerName, $"{callerFilePath}:{callerLineNumber}", stopwatch.ElapsedMilliseconds); return OperationResult.Failure(ex, stopwatch.Elapsed); } } /// /// Creates a timeout wrapper for operations /// public static async Task WithTimeoutAsync( Func> operation, TimeSpan timeout, ILogger? logger = null, [CallerMemberName] string callerName = "") { using var cts = new CancellationTokenSource(timeout); try { logger?.LogDebug("Starting operation {OperationName} with timeout {Timeout}ms", callerName, timeout.TotalMilliseconds); return await operation(cts.Token); } catch (OperationCanceledException) when (cts.Token.IsCancellationRequested) { logger?.LogWarning("Operation {OperationName} timed out after {Timeout}ms", callerName, timeout.TotalMilliseconds); throw new TimeoutException($"Operation {callerName} timed out after {timeout.TotalMilliseconds}ms"); } } /// /// Handles exceptions from plugin operations with detailed logging /// public static PluginErrorInfo HandlePluginException( Exception exception, string pluginName, string operationName, ILogger? logger = null) { var errorInfo = new PluginErrorInfo { PluginName = pluginName, OperationName = operationName, Exception = exception, Timestamp = DateTime.UtcNow, ErrorType = ClassifyError(exception), Severity = DetermineSeverity(exception), Recoverable = IsRecoverable(exception) }; logger?.Log(GetLogLevel(errorInfo.Severity), exception, "Plugin {PluginName} failed during {OperationName}: {ErrorType} - {ErrorMessage}", pluginName, operationName, errorInfo.ErrorType, exception.Message); return errorInfo; } /// /// Determines if an exception should trigger a retry /// private static bool ShouldRetry(Exception exception) { return exception switch { OperationCanceledException => false, ArgumentNullException => false, ArgumentException => false, InvalidOperationException => false, NotSupportedException => false, UnauthorizedAccessException => false, System.IO.FileNotFoundException => false, System.IO.DirectoryNotFoundException => false, System.IO.IOException => true, TimeoutException => true, _ => true }; } /// /// Calculates exponential backoff delay /// private static TimeSpan CalculateDelay(TimeSpan baseDelay, int attempt) { var exponentialDelay = TimeSpan.FromTicks(baseDelay.Ticks * (long)Math.Pow(2, attempt)); var jitter = TimeSpan.FromMilliseconds(Random.Shared.Next(0, 100)); return exponentialDelay + jitter; } /// /// Classifies the type of error for better handling /// private static string ClassifyError(Exception exception) { return exception switch { ArgumentException => "Configuration", UnauthorizedAccessException => "Security", System.IO.IOException => "IO", TimeoutException => "Timeout", OutOfMemoryException => "Memory", StackOverflowException => "Stack", OperationCanceledException => "Cancellation", _ => "General" }; } /// /// Determines the severity of an error /// private static ErrorSeverity DetermineSeverity(Exception exception) { return exception switch { OutOfMemoryException => ErrorSeverity.Critical, StackOverflowException => ErrorSeverity.Critical, UnauthorizedAccessException => ErrorSeverity.High, System.IO.FileNotFoundException => ErrorSeverity.Medium, System.IO.DirectoryNotFoundException => ErrorSeverity.Medium, ArgumentException => ErrorSeverity.Medium, TimeoutException => ErrorSeverity.Low, OperationCanceledException => ErrorSeverity.Low, _ => ErrorSeverity.Medium }; } /// /// Determines if an error is recoverable /// private static bool IsRecoverable(Exception exception) { return exception switch { OutOfMemoryException => false, StackOverflowException => false, UnauthorizedAccessException => false, System.IO.FileNotFoundException => false, System.IO.DirectoryNotFoundException => false, ArgumentException => false, TimeoutException => true, System.IO.IOException => true, _ => true }; } /// /// Gets appropriate log level for error severity /// private static LogLevel GetLogLevel(ErrorSeverity severity) { return severity switch { ErrorSeverity.Critical => LogLevel.Critical, ErrorSeverity.High => LogLevel.Error, ErrorSeverity.Medium => LogLevel.Warning, ErrorSeverity.Low => LogLevel.Information, _ => LogLevel.Warning }; } } /// /// Result of an operation with error handling /// public class OperationResult { public bool IsSuccess { get; private set; } public T? Value { get; private set; } public Exception? Exception { get; private set; } public TimeSpan Duration { get; private set; } public string? ErrorMessage => Exception?.Message; private OperationResult(bool isSuccess, T? value, Exception? exception, TimeSpan duration) { IsSuccess = isSuccess; Value = value; Exception = exception; Duration = duration; } public static OperationResult Success(T value, TimeSpan duration) { return new OperationResult(true, value, null, duration); } public static OperationResult Failure(Exception exception, TimeSpan duration) { return new OperationResult(false, default, exception, duration); } } /// /// Information about plugin errors /// public class PluginErrorInfo { public string PluginName { get; set; } = string.Empty; public string OperationName { get; set; } = string.Empty; public Exception? Exception { get; set; } public DateTime Timestamp { get; set; } public string ErrorType { get; set; } = string.Empty; public ErrorSeverity Severity { get; set; } public bool Recoverable { get; set; } } /// /// Error severity levels /// public enum ErrorSeverity { Low, Medium, High, Critical } }