using System; using System.Threading.Tasks; using System.Text; using Compliance.Domain.Models; using Compliance.DTOs; using Compliance.Infrastructure.Repositories; namespace Compliance.Services.ValidationRules { public class ReadingImpedimentValidationRule(IDistributorRepository distributorRepository) : IValidationRule { private readonly IDistributorRepository _distributorRepository = distributorRepository; private const string RULE_NAME = "Reading Impediment Validation"; // Art. 231 - Impediment documentation requirements private const int MAX_DAYS_WITHOUT_NOTIFICATION = 15; private const int MAX_CONSECUTIVE_IMPEDIMENTS = 3; private const string NOTIFICATION_ERROR = "Customer must be notified within 15 days of impediment"; private const string CONSECUTIVE_ERROR = "Maximum consecutive impediments exceeded"; private const string DOCUMENTATION_ERROR = "Insufficient impediment documentation"; private const string RESOLUTION_ERROR = "No resolution attempts documented"; private const string EVIDENCE_ERROR = "Photo evidence required for access impediment"; public int Priority => 1; // High priority as it affects reading validation public async Task ValidateAsync( BillComplianceRequest request, DistributorInformation distributorInfo, Client clientInfo) { if (!request.HasReadingImpediment) return ValidationResult.Success(RULE_NAME); var impedimentInfo = await _distributorRepository.GetReadingImpedimentInfoAsync( request.SmartCode, request.CurrentReadingDate) ?? throw new InvalidOperationException("Impediment information not found"); var errors = new List(); ValidateImpedimentDocumentation(impedimentInfo, errors); ValidateCustomerNotification(impedimentInfo, errors); ValidateConsecutiveImpediments(impedimentInfo, errors); ValidateResolutionAttempts(impedimentInfo, errors); ValidateAlternativeReading(request, impedimentInfo, errors); return errors.Count == 0 ? ValidationResult.Success(RULE_NAME) : ValidationResult.Failure(RULE_NAME, errors); } private static void ValidateImpedimentDocumentation(ReadingImpedimentInfo info, List errors) { if (string.IsNullOrEmpty(info.ImpedimentCode) || string.IsNullOrEmpty(info.Description) || info.ReportDate == default || string.IsNullOrEmpty(info.ReportedBy)) { errors.Add(DOCUMENTATION_ERROR); } // Art. 231 ยง1 - Photo evidence requirement if (info.ImpedimentCode.Contains("ACCESS", StringComparison.OrdinalIgnoreCase) && info.PhotoEvidence.Count == 0) { errors.Add(EVIDENCE_ERROR); } } private static void ValidateCustomerNotification(ReadingImpedimentInfo info, List errors) { if (info.RequiresCustomerAction) { if (!info.CustomerNotificationDate.HasValue) { errors.Add(NOTIFICATION_ERROR); } else { var notificationDelay = (info.CustomerNotificationDate.Value - info.ReportDate).Days; if (notificationDelay > MAX_DAYS_WITHOUT_NOTIFICATION) { errors.Add($"{NOTIFICATION_ERROR}. Actual delay: {notificationDelay} days"); } } } } private static void ValidateConsecutiveImpediments(ReadingImpedimentInfo info, List errors) { if (info.ConsecutiveOccurrences > MAX_CONSECUTIVE_IMPEDIMENTS) { errors.Add($"{CONSECUTIVE_ERROR}. Current consecutive: {info.ConsecutiveOccurrences}"); } } private static void ValidateResolutionAttempts(ReadingImpedimentInfo info, List errors) { if (info.ConsecutiveOccurrences > 1 && string.IsNullOrEmpty(info.ResolutionAttempts)) { errors.Add(RESOLUTION_ERROR); } } private static void ValidateAlternativeReading( BillComplianceRequest request, ReadingImpedimentInfo info, List errors) { // Art. 232 - Alternative reading methods if (string.IsNullOrEmpty(info.AlternativeReadingMethod)) { errors.Add("Alternative reading method must be documented"); } else if (info.AlternativeReadingMethod.Equals("ESTIMATION", StringComparison.OrdinalIgnoreCase)) { // Ensure estimation is properly justified if (string.IsNullOrEmpty(request.EstimationJustification)) { errors.Add("Estimation justification required for impediment-based reading"); } } } } }