using Microsoft.Extensions.Diagnostics.HealthChecks; using MongoDB.Bson; using MongoDB.Driver; using QRRapidoApp.Data; using System.Diagnostics; namespace QRRapidoApp.Services.HealthChecks { public class MongoDbHealthCheck : IHealthCheck { private readonly IServiceProvider _serviceProvider; private readonly IConfiguration _configuration; private readonly ILogger _logger; private readonly int _timeoutSeconds; private readonly bool _includeDatabaseSize; private readonly bool _testQuery; public MongoDbHealthCheck( IServiceProvider serviceProvider, IConfiguration configuration, ILogger logger) { _serviceProvider = serviceProvider; _configuration = configuration; _logger = logger; _timeoutSeconds = configuration.GetValue("HealthChecks:MongoDB:TimeoutSeconds", 5); _includeDatabaseSize = configuration.GetValue("HealthChecks:MongoDB:IncludeDatabaseSize", true); _testQuery = configuration.GetValue("HealthChecks:MongoDB:TestQuery", true); } public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { var stopwatch = Stopwatch.StartNew(); try { using var scope = _serviceProvider.CreateScope(); var mongoContext = scope.ServiceProvider.GetService(); if (mongoContext?.Database == null) { return HealthCheckResult.Degraded("MongoDB context not available - application running without database"); } using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(_timeoutSeconds)); using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); var data = new Dictionary(); // Test basic connectivity with ping var pingCommand = new BsonDocument("ping", 1); await mongoContext.Database.RunCommandAsync(pingCommand, cancellationToken: combinedCts.Token); var latencyMs = stopwatch.ElapsedMilliseconds; data["latency"] = $"{latencyMs}ms"; data["status"] = latencyMs < 100 ? "fast" : latencyMs < 500 ? "normal" : "slow"; // Test a simple query if enabled if (_testQuery) { try { var testCollection = mongoContext.Database.GetCollection("Users"); var queryStopwatch = Stopwatch.StartNew(); await testCollection.CountDocumentsAsync(new BsonDocument(), cancellationToken: combinedCts.Token); queryStopwatch.Stop(); data["lastQuery"] = "successful"; data["queryLatencyMs"] = queryStopwatch.ElapsedMilliseconds; } catch (Exception queryEx) { _logger.LogWarning(queryEx, "MongoDB health check query failed"); data["lastQuery"] = "failed"; data["queryError"] = queryEx.Message; } } // Get database size and basic stats if enabled if (_includeDatabaseSize) { try { var dbStatsCommand = new BsonDocument("dbStats", 1); var dbStats = await mongoContext.Database.RunCommandAsync(dbStatsCommand, cancellationToken: combinedCts.Token); var dataSize = dbStats.GetValue("dataSize", BsonValue.Create(0)).AsDouble; var indexSize = dbStats.GetValue("indexSize", BsonValue.Create(0)).AsDouble; var totalSizeMB = (dataSize + indexSize) / (1024 * 1024); var documentCount = dbStats.GetValue("objects", BsonValue.Create(0)).ToInt64(); data["databaseSizeMB"] = Math.Round(totalSizeMB, 1); data["documentCount"] = documentCount; data["indexSizeMB"] = Math.Round(indexSize / (1024 * 1024), 1); } catch (Exception statsEx) { _logger.LogWarning(statsEx, "Failed to get MongoDB database stats for health check"); data["databaseStatsError"] = statsEx.Message; } } // Get MongoDB version try { var serverStatus = await mongoContext.Database.RunCommandAsync( new BsonDocument("serverStatus", 1), cancellationToken: combinedCts.Token); data["version"] = serverStatus.GetValue("version", BsonValue.Create("unknown")).AsString; } catch (Exception versionEx) { _logger.LogWarning(versionEx, "Failed to get MongoDB version for health check"); data["version"] = "unknown"; } stopwatch.Stop(); data["totalCheckTimeMs"] = stopwatch.ElapsedMilliseconds; // Determine health status based on performance if (latencyMs > 2000) { return HealthCheckResult.Unhealthy($"MongoDB responding slowly ({latencyMs}ms)", data: data); } if (latencyMs > 1000) { return HealthCheckResult.Degraded($"MongoDB performance degraded ({latencyMs}ms)", data: data); } return HealthCheckResult.Healthy($"MongoDB healthy ({latencyMs}ms)", data: data); } catch (OperationCanceledException) { return HealthCheckResult.Unhealthy($"MongoDB health check timed out after {_timeoutSeconds} seconds"); } catch (Exception ex) { _logger.LogError(ex, "MongoDB health check failed"); return HealthCheckResult.Unhealthy($"MongoDB health check failed: {ex.Message}"); } } } }