using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using Haoliang.Models.Template; using Haoliang.Models.Device; using Haoliang.Models.DataCollection; using Haoliang.Data.Repositories; namespace Haoliang.Core.Services { public interface ITemplateValidationService { Task ValidateTemplateStructureAsync(CNCBrandTemplate template); Task ValidateTagMappingsAsync(CNCBrandTemplate template); Task ValidateDataParsingRulesAsync(CNCBrandTemplate template); Task> ValidateTemplateForDeviceAsync(int templateId, int deviceId); Task TestTemplateDataParsingAsync(CNCBrandTemplate template, string sampleData); Task> GetMissingRequiredTagsAsync(CNCBrandTemplate template); Task ValidateTemplateComprehensivelyAsync(CNCBrandTemplate template); Task> ValidateDeviceDataAsync(IEnumerable deviceTags, int templateId); Task ValidateTemplateCompatibilityAsync(CNCBrandTemplate template1, CNCBrandTemplate template2); } public class TemplateValidationService : ITemplateValidationService { private readonly ITemplateRepository _templateRepository; private readonly ITagMappingRepository _tagMappingRepository; private readonly ILoggingService _loggingService; public TemplateValidationService( ITemplateRepository templateRepository, ITagMappingRepository tagMappingRepository, ILoggingService loggingService) { _templateRepository = templateRepository; _tagMappingRepository = tagMappingRepository; _loggingService = loggingService; } public async Task ValidateTemplateStructureAsync(CNCBrandTemplate template) { var errors = new List(); // Check basic structure if (template == null) { errors.Add(new ValidationError { Field = "template", Message = "Template cannot be null" }); return false; } if (string.IsNullOrWhiteSpace(template.TemplateName)) { errors.Add(new ValidationError { Field = "templateName", Message = "Template name is required" }); } if (string.IsNullOrWhiteSpace(template.BrandName)) { errors.Add(new ValidationError { Field = "brandName", Message = "Brand name is required" }); } if (template.Tags == null || !template.Tags.Any()) { errors.Add(new ValidationError { Field = "tags", Message = "At least one tag must be defined" }); } // Validate tag structure if (template.Tags != null) { for (int i = 0; i < template.Tags.Count; i++) { var tag = template.Tags[i]; var tagErrors = ValidateTagStructure(tag, $"tags[{i}]"); errors.AddRange(tagErrors); } } // Validate data processing rules if (template.DataProcessingRules != null) { foreach (var rule in template.DataProcessingRules) { var ruleErrors = ValidateDataProcessingRule(rule); errors.AddRange(ruleErrors); } } // Log validation results if (errors.Any()) { await _loggingService.LogWarningAsync($"Template structure validation failed with {errors.Count} errors"); } else { await _loggingService.LogInfoAsync("Template structure validation passed"); } return !errors.Any(); } public async Task ValidateTagMappingsAsync(CNCBrandTemplate template) { var errors = new List(); if (template.Tags == null || !template.Tags.Any()) { errors.Add(new ValidationError { Field = "tags", Message = "No tags defined to validate" }); return false; } var mappings = await _tagMappingRepository.GetMappingsByTemplateAsync(template.TemplateId); var mappingDict = mappings.ToDictionary(m => m.DeviceTagId); foreach (var tag in template.Tags) { if (!string.IsNullOrWhiteSpace(tag.DeviceTagId)) { // Check if mapping exists for device tag if (!mappingDict.ContainsKey(tag.DeviceTagId)) { errors.Add(new ValidationError { Field = $"tags[{tag.SystemTagId}].deviceTagId", Message = $"No mapping found for device tag '{tag.DeviceTagId}'" }); } else { var mapping = mappingDict[tag.DeviceTagId]; if (!mapping.IsActive) { errors.Add(new ValidationError { Field = $"tags[{tag.SystemTagId}].deviceTagId", Message = $"Mapping for device tag '{tag.DeviceTagId}' is inactive" }); } } } } return !errors.Any(); } public async Task ValidateDataParsingRulesAsync(CNCBrandTemplate template) { var errors = new List(); if (template.DataProcessingRules == null || !template.DataProcessingRules.Any()) { return true; // No rules to validate } foreach (var rule in template.DataProcessingRules) { var ruleErrors = ValidateDataProcessingRule(rule); errors.AddRange(ruleErrors); } return !errors.Any(); } public async Task> ValidateTemplateForDeviceAsync(int templateId, int deviceId) { var errors = new List(); var template = await _templateRepository.GetByIdAsync(templateId); if (template == null) { errors.Add(new ValidationError { Field = "template", Message = "Template not found" }); return errors; } var device = await _templateRepository.GetDeviceByIdAsync(deviceId); // Assuming this method exists if (device == null) { errors.Add(new ValidationError { Field = "device", Message = "Device not found" }); return errors; } // Check if template is compatible with device brand if (template.BrandName != device.DeviceBrand) { errors.Add(new ValidationError { Field = "template.brand", Message = $"Template brand '{template.BrandName}' does not match device brand '{device.DeviceBrand}'" }); } // Check for missing required tags var missingTags = await GetMissingRequiredTagsAsync(template); if (missingTags.Any()) { errors.Add(new ValidationError { Field = "tags", Message = $"Missing required tags: {string.Join(", ", missingTags)}" }); } return errors; } public async Task TestTemplateDataParsingAsync(CNCBrandTemplate template, string sampleData) { try { // Parse sample data var document = JsonDocument.Parse(sampleData); var root = document.RootElement; // Extract device data var deviceTags = new List(); if (root.TryGetProperty("tags", out var tagsElement)) { foreach (var tagElement in tagsElement.EnumerateArray()) { var tag = new TagData { Id = tagElement.GetProperty("id").GetString(), Desc = tagElement.GetProperty("desc").GetString(), Quality = tagElement.GetProperty("quality").GetString(), Time = DateTime.Parse(tagElement.GetProperty("time").GetString()) }; if (tagElement.TryGetProperty("value", out var valueElement)) { tag.Value = ParseTagValue(valueElement); } deviceTags.Add(tag); } } // Test tag mapping var mappingResult = await ValidateDeviceDataAsync(deviceTags, template.TemplateId); if (mappingResult.Any(r => !r.IsValid)) { await _loggingService.LogWarningAsync($"Template data parsing test failed with {mappingResult.Count(r => !r.IsValid)} validation errors"); return false; } // Test data processing rules if (template.DataProcessingRules != null) { foreach (var rule in template.DataProcessingRules) { if (!await ExecuteDataProcessingRuleAsync(rule, deviceTags)) { await _loggingService.LogWarningAsync($"Data processing rule '{rule.RuleName}' failed during test"); return false; } } } await _loggingService.LogInfoAsync("Template data parsing test passed"); return true; } catch (Exception ex) { await _loggingService.LogErrorAsync($"Template data parsing test failed: {ex.Message}", ex); return false; } } public async Task> GetMissingRequiredTagsAsync(CNCBrandTemplate template) { var missingTags = new List(); if (template == null || template.Tags == null) return missingTags; var requiredTags = template.Tags .Where(t => t.IsRequired) .Select(t => t.DeviceTagId) .Where(id => !string.IsNullOrWhiteSpace(id)) .ToList(); if (!requiredTags.Any()) return missingTags; var mappings = await _tagMappingRepository.GetMappingsByTemplateAsync(template.TemplateId); var mappedTags = mappings .Where(m => m.IsActive) .Select(m => m.DeviceTagId) .ToHashSet(); foreach (var requiredTag in requiredTags) { if (!mappedTags.Contains(requiredTag)) { missingTags.Add(requiredTag); } } return missingTags; } public async Task ValidateTemplateComprehensivelyAsync(CNCBrandTemplate template) { var report = new ValidationReport { TemplateId = template.TemplateId, TemplateName = template.TemplateName, ValidationTime = DateTime.Now, Checks = new List { new ValidationCheck { Name = "Structure Validation", Passed = await ValidateTemplateStructureAsync(template), Errors = new List() }, new ValidationCheck { Name = "Tag Mapping Validation", Passed = await ValidateTagMappingsAsync(template), Errors = new List() }, new ValidationCheck { Name = "Data Parsing Rules Validation", Passed = await ValidateDataProcessingRulesAsync(template), Errors = new List() } } }; // Aggregate all errors report.HasErrors = report.Checks.Any(c => !c.Passed); report.TotalChecks = report.Checks.Count; report.PassedChecks = report.Checks.Count(c => c.Passed); report.FailedChecks = report.Checks.Count(c => !c.Passed); await _loggingService.LogInformationAsync($"Comprehensive template validation: {report.PassedChecks}/{report.TotalChecks} checks passed"); return report; } public async Task> ValidateDeviceDataAsync(IEnumerable deviceTags, int templateId) { var results = new List(); var template = await _templateRepository.GetByIdAsync(templateId); if (template == null) { results.Add(new TagValidationResult { SystemTagId = "template", IsValid = false, ErrorMessage = "Template not found" }); return results; } var templateTags = template.Tags ?? new List(); var mappings = await _tagMappingRepository.GetMappingsByTemplateAsync(templateId); var mappingDict = mappings.ToDictionary(m => m.DeviceTagId); foreach (var templateTag in templateTags) { var result = new TagValidationResult { SystemTagId = templateTag.SystemTagId, ExpectedDataType = templateTag.DataType, IsRequired = templateTag.IsRequired }; if (!string.IsNullOrWhiteSpace(templateTag.DeviceTagId)) { // Find corresponding device tag var deviceTag = deviceTags.FirstOrDefault(t => t.Id == templateTag.DeviceTagId); if (deviceTag == null) { if (templateTag.IsRequired) { result.IsValid = false; result.ErrorMessage = $"Required device tag '{templateTag.DeviceTagId}' not found"; } else { result.IsValid = true; result.Message = "Optional tag not present"; } } else { // Validate device tag result.DeviceTagId = templateTag.DeviceTagId; result.DeviceValue = deviceTag.Value; result.Quality = deviceTag.Quality; if (!ValidateTagValue(deviceTag, templateTag)) { result.IsValid = false; result.ErrorMessage = $"Tag value validation failed for '{templateTag.DeviceTagId}'"; } else { result.IsValid = true; result.MappedValue = ApplyTagMapping(deviceTag, mappingDict[templateTag.DeviceTagId]); } } } else { result.IsValid = true; result.Message = "No device tag mapping defined"; } results.Add(result); } return results; } public async Task ValidateTemplateCompatibilityAsync(CNCBrandTemplate template1, CNCBrandTemplate template2) { if (template1 == null || template2 == null) return false; if (template1.TemplateId == template2.TemplateId) return true; // Same template is always compatible // Check if same brand if (template1.BrandName != template2.BrandName) { await _loggingService.LogInfoAsync($"Templates have different brands: {template1.BrandName} vs {template2.BrandName}"); return false; } // Check tag compatibility var tags1 = template1.Tags ?? new List(); var tags2 = template2.Tags ?? new List(); // Count common tags var commonTags = tags1.Intersect(tags2, new TagTemplateComparer()); var compatibilityScore = (double)commonTags.Count() / Math.Max(tags1.Count, tags2.Count); await _loggingService.LogInfoAsync($"Template compatibility score: {compatibilityScore:P1}"); return compatibilityScore >= 0.8; // 80% compatibility threshold } private List ValidateTagStructure(TagTemplate tag, string fieldPath) { var errors = new List(); if (string.IsNullOrWhiteSpace(tag.SystemTagId)) { errors.Add(new ValidationError { Field = $"{fieldPath}.systemTagId", Message = "System tag ID is required" }); } if (!string.IsNullOrWhiteSpace(tag.DeviceTagId)) { if (string.IsNullOrWhiteSpace(tag.DataType)) { errors.Add(new ValidationError { Field = $"{fieldPath}.dataType", Message = "Data type is required when device tag ID is specified" }); } } if (!string.IsNullOrWhiteSpace(tag.DataType)) { var validTypes = new[] { "int", "decimal", "bool", "string", "datetime", "double" }; if (!validTypes.Contains(tag.DataType.ToLower())) { errors.Add(new ValidationError { Field = $"{fieldPath}.dataType", Message = $"Invalid data type: {tag.DataType}" }); } } // Validate regex pattern if provided if (!string.IsNullOrWhiteSpace(tag.ValidationRegex)) { try { Regex.IsMatch("test", tag.ValidationRegex); // Test if regex is valid } catch { errors.Add(new ValidationError { Field = $"{fieldPath}.validationRegex", Message = "Invalid regular expression pattern" }); } } return errors; } private List ValidateDataProcessingRule(DataProcessingRule rule) { var errors = new List(); if (string.IsNullOrWhiteSpace(rule.RuleName)) { errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].ruleName", Message = "Rule name is required" }); } if (string.IsNullOrWhiteSpace(rule.Condition)) { errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].condition", Message = "Condition is required" }); } if (string.IsNullOrWhiteSpace(rule.Action)) { errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].action", Message = "Action is required" }); } // Validate condition syntax (simple check) if (!string.IsNullOrWhiteSpace(rule.Condition) && !IsValidCondition(rule.Condition)) { errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].condition", Message = "Invalid condition syntax" }); } return errors; } private bool IsValidCondition(string condition) { // Simple condition validation (in real implementation, use expression parser) var validOperators = new[] { ">", "<", ">=", "<=", "==", "!=", "&&", "||", "(", ")", "true", "false" }; var parts = condition.Split(' '); foreach (var part in parts) { if (!string.IsNullOrWhiteSpace(part) && !validOperators.Contains(part) && !double.TryParse(part, out _)) { return false; } } return true; } private object ParseTagValue(JsonElement valueElement) { if (valueElement.ValueKind == JsonValueKind.String) { return valueElement.GetString(); } else if (valueElement.ValueKind == JsonValueKind.Number) { if (valueElement.TryGetInt32(out int intValue)) return intValue; else if (valueElement.TryGetDecimal(out decimal decimalValue)) return decimalValue; else return valueElement.GetDouble(); } else if (valueElement.ValueKind == JsonValueKind.True || valueElement.ValueKind == JsonValueKind.False) { return valueElement.GetBoolean(); } else { return valueElement.ToString(); } } private bool ValidateTagValue(TagData deviceTag, TagTemplate templateTag) { if (deviceTag.Value == null) return !templateTag.IsRequired; try { // Validate data type switch (templateTag.DataType.ToLower()) { case "int": return int.TryParse(deviceTag.Value.ToString(), out _); case "decimal": return decimal.TryParse(deviceTag.Value.ToString(), out _); case "bool": return bool.TryParse(deviceTag.Value.ToString(), out _); case "datetime": return DateTime.TryParse(deviceTag.Value.ToString(), out _); default: return true; // String type accepts any value } } catch { return false; } } private TagData ApplyTagMapping(TagData deviceTag, TagMapping mapping) { return new TagData { Id = mapping.SystemTagId, Desc = mapping.Description ?? deviceTag.Desc, Quality = deviceTag.Quality, Time = deviceTag.Time, Value = ConvertTagValue(deviceTag.Value, mapping.DataType, mapping.ConversionFormula) }; } private async Task ExecuteDataProcessingRuleAsync(DataProcessingRule rule, IEnumerable deviceTags) { // Simple rule execution (in real implementation, use expression parser) try { // This is a simplified implementation // In a real system, you would parse and execute the condition and action if (rule.Condition.Contains("temperature") && deviceTags.Any(t => t.Id == "temperature")) { var tempTag = deviceTags.First(t => t.Id == "temperature"); var tempValue = Convert.ToDouble(tempTag.Value); if (tempValue > 100 && rule.Action.Contains("alert")) { return true; // Rule executed successfully } } return true; } catch (Exception ex) { await _loggingService.LogErrorAsync($"Failed to execute data processing rule '{rule.RuleName}': {ex.Message}"); return false; } } } // Supporting classes and interfaces public class ValidationReport { public int TemplateId { get; set; } public string TemplateName { get; set; } public DateTime ValidationTime { get; set; } public List Checks { get; set; } public bool HasErrors { get; set; } public int TotalChecks { get; set; } public int PassedChecks { get; set; } public int FailedChecks { get; set; } } public class ValidationCheck { public string Name { get; set; } public bool Passed { get; set; } public List Errors { get; set; } } public class TagValidationResult { public string SystemTagId { get; set; } public string DeviceTagId { get; set; } public object DeviceValue { get; set; } public object MappedValue { get; set; } public string ExpectedDataType { get; set; } public string Quality { get; set; } public bool IsRequired { get; set; } public bool IsValid { get; set; } public string ErrorMessage { get; set; } public string Message { get; set; } } public class TagTemplateComparer : IEqualityComparer { public bool Equals(TagTemplate x, TagTemplate y) { return x?.SystemTagId == y?.SystemTagId; } public int GetHashCode(TagTemplate obj) { return obj?.SystemTagId?.GetHashCode() ?? 0; } } }