using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Text.Json; using System.Threading.Tasks; using Microsoft.Extensions.Diagnostics.HealthChecks; using Serilog; using ZB.MOM.WW.LmxProxy.Host.Domain; namespace ZB.MOM.WW.LmxProxy.Host.Services { /// /// Service for collecting and formatting status information from various LmxProxy components /// public class StatusReportService { private static readonly ILogger Logger = Log.ForContext(); private readonly DetailedHealthCheckService? _detailedHealthCheckService; private readonly HealthCheckService _healthCheckService; private readonly PerformanceMetrics _performanceMetrics; private readonly IScadaClient _scadaClient; private readonly SubscriptionManager _subscriptionManager; /// /// Initializes a new instance of the StatusReportService class /// public StatusReportService( IScadaClient scadaClient, SubscriptionManager subscriptionManager, PerformanceMetrics performanceMetrics, HealthCheckService healthCheckService, DetailedHealthCheckService? detailedHealthCheckService = null) { _scadaClient = scadaClient ?? throw new ArgumentNullException(nameof(scadaClient)); _subscriptionManager = subscriptionManager ?? throw new ArgumentNullException(nameof(subscriptionManager)); _performanceMetrics = performanceMetrics ?? throw new ArgumentNullException(nameof(performanceMetrics)); _healthCheckService = healthCheckService ?? throw new ArgumentNullException(nameof(healthCheckService)); _detailedHealthCheckService = detailedHealthCheckService; } /// /// Generates a comprehensive status report as HTML /// public async Task GenerateHtmlReportAsync() { try { StatusData statusData = await CollectStatusDataAsync(); return GenerateHtmlFromStatusData(statusData); } catch (Exception ex) { Logger.Error(ex, "Error generating HTML status report"); return GenerateErrorHtml(ex); } } /// /// Generates a comprehensive status report as JSON /// public async Task GenerateJsonReportAsync() { try { StatusData statusData = await CollectStatusDataAsync(); return JsonSerializer.Serialize(statusData, new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); } catch (Exception ex) { Logger.Error(ex, "Error generating JSON status report"); return JsonSerializer.Serialize(new { error = ex.Message }, new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); } } /// /// Checks if the service is healthy /// public async Task IsHealthyAsync() { try { HealthCheckResult healthResult = await _healthCheckService.CheckHealthAsync(new HealthCheckContext()); return healthResult.Status == HealthStatus.Healthy; } catch (Exception ex) { Logger.Error(ex, "Error checking health status"); return false; } } /// /// Collects status data from all components /// private async Task CollectStatusDataAsync() { var statusData = new StatusData { Timestamp = DateTime.UtcNow, ServiceName = "ZB.MOM.WW.LmxProxy.Host", Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "Unknown" }; // Collect connection status statusData.Connection = new ConnectionStatus { IsConnected = _scadaClient.IsConnected, State = _scadaClient.ConnectionState.ToString(), NodeName = "N/A", // Could be extracted from configuration if needed GalaxyName = "N/A" // Could be extracted from configuration if needed }; // Collect subscription statistics SubscriptionStats subscriptionStats = _subscriptionManager.GetSubscriptionStats(); statusData.Subscriptions = new SubscriptionStatus { TotalClients = subscriptionStats.TotalClients, TotalTags = subscriptionStats.TotalTags, ActiveSubscriptions = subscriptionStats.TotalTags // Assuming same for simplicity }; // Collect performance metrics Dictionary perfMetrics = _performanceMetrics.GetStatistics(); statusData.Performance = new PerformanceStatus { TotalOperations = perfMetrics.Values.Sum(m => m.TotalCount), AverageSuccessRate = perfMetrics.Count > 0 ? perfMetrics.Values.Average(m => m.SuccessRate) : 1.0, Operations = perfMetrics.ToDictionary( kvp => kvp.Key, kvp => new OperationStatus { TotalCount = kvp.Value.TotalCount, SuccessRate = kvp.Value.SuccessRate, AverageMilliseconds = kvp.Value.AverageMilliseconds, MinMilliseconds = kvp.Value.MinMilliseconds, MaxMilliseconds = kvp.Value.MaxMilliseconds }) }; // Collect health check results try { HealthCheckResult healthResult = await _healthCheckService.CheckHealthAsync(new HealthCheckContext()); statusData.Health = new HealthInfo { Status = healthResult.Status.ToString(), Description = healthResult.Description ?? "", Data = healthResult.Data?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToString() ?? "") ?? new Dictionary() }; // Collect detailed health check if available if (_detailedHealthCheckService != null) { HealthCheckResult detailedHealthResult = await _detailedHealthCheckService.CheckHealthAsync(new HealthCheckContext()); statusData.DetailedHealth = new HealthInfo { Status = detailedHealthResult.Status.ToString(), Description = detailedHealthResult.Description ?? "", Data = detailedHealthResult.Data?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToString() ?? "") ?? new Dictionary() }; } } catch (Exception ex) { Logger.Error(ex, "Error collecting health check data"); statusData.Health = new HealthInfo { Status = "Error", Description = $"Health check failed: {ex.Message}", Data = new Dictionary() }; } return statusData; } /// /// Generates HTML from status data /// private static string GenerateHtmlFromStatusData(StatusData statusData) { var html = new StringBuilder(); html.AppendLine(""); html.AppendLine(""); html.AppendLine(""); html.AppendLine(" LmxProxy Status"); html.AppendLine(" "); html.AppendLine(" "); html.AppendLine(" "); html.AppendLine(" "); html.AppendLine(""); html.AppendLine(""); html.AppendLine("
"); // Header html.AppendLine("
"); html.AppendLine("

LmxProxy Status Dashboard

"); html.AppendLine($"

Service: {statusData.ServiceName} | Version: {statusData.Version}

"); html.AppendLine("
"); html.AppendLine("
"); // Connection Status Card string connectionClass = statusData.Connection.IsConnected ? "status-connected" : "status-disconnected"; string connectionStatusText = statusData.Connection.IsConnected ? "Connected" : "Disconnected"; string connectionStatusClass = statusData.Connection.IsConnected ? "status-healthy" : "status-error"; html.AppendLine($"
"); html.AppendLine("

MxAccess Connection

"); html.AppendLine( $"

Status: {connectionStatusText}

"); html.AppendLine( $"

State: {statusData.Connection.State}

"); html.AppendLine("
"); // Subscription Status Card html.AppendLine("
"); html.AppendLine("

Subscriptions

"); html.AppendLine( $"

Total Clients: {statusData.Subscriptions.TotalClients}

"); html.AppendLine( $"

Total Tags: {statusData.Subscriptions.TotalTags}

"); html.AppendLine( $"

Active Subscriptions: {statusData.Subscriptions.ActiveSubscriptions}

"); html.AppendLine("
"); // Performance Status Card html.AppendLine("
"); html.AppendLine("

Performance

"); html.AppendLine( $"

Total Operations: {statusData.Performance.TotalOperations:N0}

"); html.AppendLine( $"

Success Rate: {statusData.Performance.AverageSuccessRate:P2}

"); html.AppendLine("
"); // Health Status Card string healthStatusClass = statusData.Health.Status.ToLowerInvariant() switch { "healthy" => "status-healthy", "degraded" => "status-warning", _ => "status-error" }; html.AppendLine("
"); html.AppendLine("

Health Status

"); html.AppendLine( $"

Status: {statusData.Health.Status}

"); html.AppendLine( $"

Description: {statusData.Health.Description}

"); html.AppendLine("
"); html.AppendLine("
"); // Performance Metrics Table if (statusData.Performance.Operations.Any()) { html.AppendLine("
"); html.AppendLine("

Operation Performance Metrics

"); html.AppendLine(" "); html.AppendLine(" "); html.AppendLine(" "); html.AppendLine(" "); html.AppendLine(" "); html.AppendLine(" "); html.AppendLine(" "); html.AppendLine(" "); html.AppendLine(" "); foreach (KeyValuePair operation in statusData.Performance.Operations) { html.AppendLine(" "); html.AppendLine($" "); html.AppendLine($" "); html.AppendLine($" "); html.AppendLine($" "); html.AppendLine($" "); html.AppendLine($" "); html.AppendLine(" "); } html.AppendLine("
OperationCountSuccess RateAvg (ms)Min (ms)Max (ms)
{operation.Key}{operation.Value.TotalCount:N0}{operation.Value.SuccessRate:P2}{operation.Value.AverageMilliseconds:F2}{operation.Value.MinMilliseconds:F2}{operation.Value.MaxMilliseconds:F2}
"); html.AppendLine("
"); } // Timestamp html.AppendLine( $"
Last updated: {statusData.Timestamp:yyyy-MM-dd HH:mm:ss} UTC
"); html.AppendLine("
"); html.AppendLine(""); html.AppendLine(""); return html.ToString(); } /// /// Generates error HTML when status collection fails /// private static string GenerateErrorHtml(Exception ex) { return $@" LmxProxy Status - Error

LmxProxy Status Dashboard

Error Loading Status

An error occurred while collecting status information:

{ex.Message}

Last updated: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC
"; } } /// /// Data structure for holding complete status information /// public class StatusData { public DateTime Timestamp { get; set; } public string ServiceName { get; set; } = ""; public string Version { get; set; } = ""; public ConnectionStatus Connection { get; set; } = new(); public SubscriptionStatus Subscriptions { get; set; } = new(); public PerformanceStatus Performance { get; set; } = new(); public HealthInfo Health { get; set; } = new(); public HealthInfo? DetailedHealth { get; set; } } /// /// Connection status information /// public class ConnectionStatus { public bool IsConnected { get; set; } public string State { get; set; } = ""; public string NodeName { get; set; } = ""; public string GalaxyName { get; set; } = ""; } /// /// Subscription status information /// public class SubscriptionStatus { public int TotalClients { get; set; } public int TotalTags { get; set; } public int ActiveSubscriptions { get; set; } } /// /// Performance status information /// public class PerformanceStatus { public long TotalOperations { get; set; } public double AverageSuccessRate { get; set; } public Dictionary Operations { get; set; } = new(); } /// /// Individual operation status /// public class OperationStatus { public long TotalCount { get; set; } public double SuccessRate { get; set; } public double AverageMilliseconds { get; set; } public double MinMilliseconds { get; set; } public double MaxMilliseconds { get; set; } } /// /// Health check status information /// public class HealthInfo { public string Status { get; set; } = ""; public string Description { get; set; } = ""; public Dictionary Data { get; set; } = new(); } }