using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Haoliang.Models.System; using Haoliang.Models.Device; using Haoliang.Data.Repositories; namespace Haoliang.Core.Services { public interface IAlarmNotificationService { Task SendAlarmNotificationAsync(Alarm alarm); Task SendBulkAlarmNotificationsAsync(IEnumerable alarms); Task SendSmsNotificationAsync(string phoneNumber, string message); Task SendEmailNotificationAsync(string email, string subject, string message); Task SendWechatNotificationAsync(string openId, string message); Task> GetNotificationHistoryAsync(DateTime startDate, DateTime endDate); Task ConfigureNotificationChannelAsync(NotificationChannel channel); Task> GetAvailableChannelsAsync(); Task TestNotificationChannelAsync(NotificationChannel channel); Task GetFailedNotificationCountAsync(DateTime date); Task ResendFailedNotificationsAsync(int maxRetries = 3); } public class AlarmNotificationService : IAlarmNotificationService { private readonly IAlarmNotificationRepository _notificationRepository; private readonly ISystemConfigRepository _configRepository; private readonly ILoggingService _loggingService; private readonly INotificationSender _smsSender; private readonly INotificationSender _emailSender; private readonly INotificationSender _wechatSender; private readonly ConcurrentDictionary _channels; public AlarmNotificationService( IAlarmNotificationRepository notificationRepository, ISystemConfigRepository configRepository, ILoggingService loggingService, INotificationSender smsSender, INotificationSender emailSender, INotificationSender wechatSender) { _notificationRepository = notificationRepository; _configRepository = configRepository; _loggingService = loggingService; _smsSender = smsSender; _emailSender = emailSender; _wechatSender = wechatSender; _channels = new ConcurrentDictionary(); } public async Task SendAlarmNotificationAsync(Alarm alarm) { if (alarm == null) throw new ArgumentNullException(nameof(alarm)); if (!alarm.IsActive) { await _loggingService.LogDebugAsync($"Skipping notification for inactive alarm {alarm.AlarmId}"); return; } // Get notification channels for this alarm var channels = await GetNotificationChannelsForAlarm(alarm); foreach (var channel in channels) { try { await SendNotificationViaChannelAsync(alarm, channel); } catch (Exception ex) { await _loggingService.LogErrorAsync($"Failed to send alarm {alarm.AlarmId} via {channel.ChannelType}: {ex.Message}", ex); } } } public async Task SendBulkAlarmNotificationsAsync(IEnumerable alarms) { if (alarms == null) throw new ArgumentNullException(nameof(alarms)); var alarmList = alarms.ToList(); await _loggingService.LogInformationAsync($"Processing bulk notifications for {alarmList.Count} alarms"); var notificationTasks = alarmList.Select(alarm => SendAlarmNotificationAsync(alarm)); await Task.WhenAll(notificationTasks); await _loggingService.LogInformationAsync($"Completed bulk notifications for {alarmList.Count} alarms"); } public async Task SendSmsNotificationAsync(string phoneNumber, string message) { try { var smsConfig = await GetSmsConfigurationAsync(); if (!smsConfig.IsEnabled) { await _loggingService.LogWarningAsync("SMS notifications are disabled"); return false; } var result = await _smsSender.SendAsync(phoneNumber, message, smsConfig); if (result.Success) { await _loggingService.LogInformationAsync($"SMS sent successfully to {phoneNumber}"); } else { await _loggingService.LogErrorAsync($"SMS failed to {phoneNumber}: {result.ErrorMessage}"); } return result.Success; } catch (Exception ex) { await _loggingService.LogErrorAsync($"SMS notification error: {ex.Message}", ex); return false; } } public async Task SendEmailNotificationAsync(string email, string subject, string message) { try { var emailConfig = await GetEmailConfigurationAsync(); if (!emailConfig.IsEnabled) { await _loggingService.LogWarningAsync("Email notifications are disabled"); return false; } var result = await _emailSender.SendAsync(email, subject, message, emailConfig); if (result.Success) { await _loggingService.LogInformationAsync($"Email sent successfully to {email}"); } else { await _loggingService.LogErrorAsync($"Email failed to {email}: {result.ErrorMessage}"); } return result.Success; } catch (Exception ex) { await _loggingService.LogErrorAsync($"Email notification error: {ex.Message}", ex); return false; } } public async Task SendWechatNotificationAsync(string openId, string message) { try { var wechatConfig = await GetWechatConfigurationAsync(); if (!wechatConfig.IsEnabled) { await _loggingService.LogWarningAsync("WeChat notifications are disabled"); return false; } var result = await _wechatSender.SendAsync(openId, message, wechatConfig); if (result.Success) { await _loggingService.LogInformationAsync($"WeChat message sent successfully to {openId}"); } else { await _loggingService.LogErrorAsync($"WeChat message failed to {openId}: {result.ErrorMessage}"); } return result.Success; } catch (Exception ex) { await _loggingService.LogErrorAsync($"WeChat notification error: {ex.Message}", ex); return false; } } public async Task> GetNotificationHistoryAsync(DateTime startDate, DateTime endDate) { return await _notificationRepository.GetNotificationsByDateRangeAsync(startDate, endDate); } public async Task ConfigureNotificationChannelAsync(NotificationChannel channel) { if (channel == null) throw new ArgumentNullException(nameof(channel)); // Validate channel configuration if (!await ValidateChannelConfigurationAsync(channel)) return false; // Save channel configuration await _configRepository.UpsertAsync(new SystemConfig { ConfigKey = $"notification_{channel.ChannelType}_enabled", ConfigValue = channel.IsEnabled.ToString(), Category = "Notification", Description = $"Enable {channel.ChannelType} notifications" }); await _configRepository.UpsertAsync(new SystemConfig { ConfigKey = $"notification_{channel.ChannelType}_config", ConfigValue = System.Text.Json.JsonSerializer.Serialize(channel.Settings), Category = "Notification", Description = $"{channel.ChannelType} notification settings" }); _channels[channel.ChannelType] = channel; await _loggingService.LogInformationAsync($"Configured notification channel: {channel.ChannelType}"); return true; } public async Task> GetAvailableChannelsAsync() { var channels = new List(); // SMS Channel if (await IsChannelEnabledAsync("SMS")) { channels.Add(new NotificationChannel { ChannelType = "SMS", IsEnabled = true, Settings = await GetSmsConfigurationAsync() }); } // Email Channel if (await IsChannelEnabledAsync("Email")) { channels.Add(new NotificationChannel { ChannelType = "Email", IsEnabled = true, Settings = await GetEmailConfigurationAsync() }); } // WeChat Channel if (await IsChannelEnabledAsync("WeChat")) { channels.Add(new NotificationChannel { ChannelType = "WeChat", IsEnabled = true, Settings = await GetWechatConfigurationAsync() }); } return channels; } public async Task TestNotificationChannelAsync(NotificationChannel channel) { if (channel == null) throw new ArgumentNullException(nameof(channel)); var testMessage = $"This is a test notification from CNC System at {DateTime.Now:yyyy-MM-dd HH:mm:ss}"; switch (channel.ChannelType) { case "SMS": await SendSmsNotificationAsync(channel.TestPhoneNumber, testMessage); break; case "Email": await SendEmailNotificationAsync(channel.TestEmail, "CNC System Test", testMessage); break; case "WeChat": await SendWechatNotificationAsync(channel.TestOpenId, testMessage); break; default: throw new ArgumentException($"Unsupported channel type: {channel.ChannelType}"); } await _loggingService.LogInformationAsync($"Test notification sent via {channel.ChannelType}"); } public async Task GetFailedNotificationCountAsync(DateTime date) { var startOfDay = date.Date; var endOfDay = startOfDay.AddDays(1); return await _notificationRepository.GetFailedNotificationsCountAsync(startOfDay, endOfDay); } public async Task ResendFailedNotificationsAsync(int maxRetries = 3) { var failedNotifications = await _notificationRepository.GetFailedNotificationsAsync(maxRetries); if (!failedNotifications.Any()) { await _loggingService.LogInformationAsync("No failed notifications to resend"); return true; } var successCount = 0; var retryTasks = failedNotifications.Select(async notification => { try { await RetryFailedNotificationAsync(notification); successCount++; } catch (Exception ex) { await _loggingService.LogErrorAsync($"Failed to resend notification {notification.NotificationId}: {ex.Message}", ex); } }); await Task.WhenAll(retryTasks); await _loggingService.LogInformationAsync($"Resent {successCount}/{failedNotifications.Count} failed notifications"); return successCount > 0; } private async Task> GetNotificationChannelsForAlarm(Alarm alarm) { var channels = new List(); var alarmType = alarm.AlarmType.ToLower(); // Get global notification settings var smsEnabled = await IsChannelEnabledAsync("SMS"); var emailEnabled = await IsChannelEnabledAsync("Email"); var wechatEnabled = await IsChannelEnabledAsync("WeChat"); // High severity alarms get all channels if (alarm.AlarmSeverity == AlarmSeverity.Critical || alarm.AlarmSeverity == AlarmSeverity.High) { if (smsEnabled) channels.Add(await CreateSmsChannelAsync()); if (emailEnabled) channels.Add(await CreateEmailChannelAsync()); if (wechatEnabled) channels.Add(await CreateWechatChannelAsync()); } // Medium severity alarms get email and SMS else if (alarm.AlarmSeverity == AlarmSeverity.Medium) { if (emailEnabled) channels.Add(await CreateEmailChannelAsync()); if (smsEnabled) channels.Add(await CreateSmsChannelAsync()); } // Low severity alarms get email only else if (alarm.AlarmSeverity == AlarmSeverity.Low && emailEnabled) { channels.Add(await CreateEmailChannelAsync()); } return channels; } private async Task SendNotificationViaChannelAsync(Alarm alarm, NotificationChannel channel) { var subject = $"CNC Alarm: {alarm.AlarmType} - {alarm.DeviceName}"; var message = CreateAlarmMessage(alarm); switch (channel.ChannelType) { case "SMS": await SendSmsNotificationAsync(channel.Recipients.FirstOrDefault(), message); break; case "Email": await SendEmailNotificationAsync(channel.Recipients.FirstOrDefault(), subject, message); break; case "WeChat": await SendWechatNotificationAsync(channel.Recipients.FirstOrDefault(), message); break; } } private string CreateAlarmMessage(Alarm alarm) { return $@" 🚨 CNC System Alarm Alert 🚨 Device: {alarm.DeviceName} ({alarm.DeviceCode}) Type: {alarm.AlarmType} Severity: {alarm.AlarmSeverity} Time: {alarm.CreateTime:yyyy-MM-dd HH:mm:ss} Description: {alarm.Title} Please take immediate action. ".Trim(); } private async Task GetSmsConfigurationAsync() { // Get SMS configuration from database or use defaults return new NotificationSettings { IsEnabled = true, ApiKey = await _configRepository.GetValueAsync("sms_api_key") ?? "", ApiSecret = await _configRepository.GetValueAsync("sms_api_secret") ?? "", Provider = await _configRepository.GetValueAsync("sms_provider") ?? "twilio" }; } private async Task GetEmailConfigurationAsync() { return new NotificationSettings { IsEnabled = true, SmtpServer = await _configRepository.GetValueAsync("smtp_server") ?? "smtp.gmail.com", SmtpPort = int.Parse(await _configRepository.GetValueAsync("smtp_port") ?? "587"), Username = await _configRepository.GetValueAsync("smtp_username") ?? "", Password = await _configRepository.GetValueAsync("smtp_password") ?? "", UseSsl = bool.Parse(await _configRepository.GetValueAsync("smtp_ssl") ?? "true") }; } private async Task GetWechatConfigurationAsync() { return new NotificationSettings { IsEnabled = true, AppId = await _configRepository.GetValueAsync("wechat_app_id") ?? "", AppSecret = await _configRepository.GetValueAsync("wechat_app_secret") ?? "", TemplateId = await _configRepository.GetValueAsync("wechat_template_id") ?? "" }; } private async Task IsChannelEnabledAsync(string channelType) { var configKey = $"notification_{channelType}_enabled"; var configValue = await _configRepository.GetValueAsync(configKey); return bool.TryParse(configValue, out bool enabled) && enabled; } private async Task ValidateChannelConfigurationAsync(NotificationChannel channel) { // Add validation logic based on channel type switch (channel.ChannelType) { case "SMS": return !string.IsNullOrEmpty(channel.TestPhoneNumber); case "Email": return !string.IsNullOrEmpty(channel.TestEmail); case "WeChat": return !string.IsNullOrEmpty(channel.TestOpenId); default: return false; } } private async Task CreateSmsChannelAsync() { return new NotificationChannel { ChannelType = "SMS", IsEnabled = true, Recipients = await GetSmsRecipientsAsync(), TestPhoneNumber = await _configRepository.GetValueAsync("sms_test_phone") ?? "+1234567890", Settings = await GetSmsConfigurationAsync() }; } private async Task CreateEmailChannelAsync() { return new NotificationChannel { ChannelType = "Email", IsEnabled = true, Recipients = await GetEmailRecipientsAsync(), TestEmail = await _configRepository.GetValueAsync("email_test_address") ?? "test@example.com", Settings = await GetEmailConfigurationAsync() }; } private async Task CreateWechatChannelAsync() { return new NotificationChannel { ChannelType = "WeChat", IsEnabled = true, Recipients = await GetWechatRecipientsAsync(), TestOpenId = await _configRepository.GetValueAsync("wechat_test_openid") ?? "test_openid", Settings = await GetWechatConfigurationAsync() }; } private async Task> GetSmsRecipientsAsync() { var configValue = await _configRepository.GetValueAsync("sms_recipients"); return string.IsNullOrEmpty(configValue) ? new List() : configValue.Split(',').Select(s => s.Trim()).ToList(); } private async Task> GetEmailRecipientsAsync() { var configValue = await _configRepository.GetValueAsync("email_recipients"); return string.IsNullOrEmpty(configValue) ? new List() : configValue.Split(',').Select(s => s.Trim()).ToList(); } private async Task> GetWechatRecipientsAsync() { var configValue = await _configRepository.GetValueAsync("wechat_recipients"); return string.IsNullOrEmpty(configValue) ? new List() : configValue.Split(',').Select(s => s.Trim()).ToList(); } private async Task RetryFailedNotificationAsync(AlarmNotification notification) { // Retry logic here await _notificationRepository.MarkAsRetriedAsync(notification.NotificationId); } } // Supporting interfaces and classes public interface INotificationSender { Task SendAsync(string recipient, string message, NotificationSettings settings); } public class NotificationResult { public bool Success { get; set; } public string ErrorMessage { get; set; } public string ReferenceId { get; set; } } public class NotificationSettings { public bool IsEnabled { get; set; } public string ApiKey { get; set; } public string ApiSecret { get; set; } public string Provider { get; set; } public string SmtpServer { get; set; } public int SmtpPort { get; set; } public string Username { get; set; } public string Password { get; set; } public bool UseSsl { get; set; } public string AppId { get; set; } public string AppSecret { get; set; } public string TemplateId { get; set; } } // Additional repository interface for alarm notifications public interface IAlarmNotificationRepository : IRepository { Task> GetNotificationsByDateRangeAsync(DateTime startDate, DateTime endDate); Task> GetFailedNotificationsAsync(int maxRetries = 3); Task GetFailedNotificationsCountAsync(DateTime startDate, DateTime endDate); Task MarkAsRetriedAsync(int notificationId); Task> GetNotificationsByAlarmAsync(int alarmId); } }