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 GroupSpecificRulesValidationRule(IDistributorRepository distributorRepository) : IValidationRule { private readonly IDistributorRepository _distributorRepository = distributorRepository; private const string RULE_NAME = "Group-Specific Rules Validation"; // Art. 297-299 - Rural and Irrigation Requirements private const decimal MIN_IRRIGATION_DISCOUNT = 0.10m; private const decimal MAX_IRRIGATION_DISCOUNT = 0.90m; private const string SCHEDULE_ERROR = "Irrigation schedule must be documented for irrigation consumers"; private const string SEASON_ERROR = "Invalid seasonal period configuration"; private const string DISCOUNT_ERROR = "Invalid irrigation discount"; private const string ACTIVITY_ERROR = "Activity-specific discount not properly documented"; public int Priority => 3; // Run after basic and consumption validation public async Task ValidateAsync( BillComplianceRequest request, DistributorInformation distributorInfo, Client clientInfo) { var groupRules = await _distributorRepository.GetGroupSpecificRulesInfoAsync( request.ConsumerGroup, request.Subgroup, request.Month) ?? throw new InvalidOperationException("Group rules not found"); var errors = new List(); ValidateRuralClassification(request, groupRules, errors); ValidateIrrigationRules(request, groupRules, errors); ValidateSeasonalRules(request, groupRules, errors); ValidateActivityDiscounts(request, groupRules, errors); return errors.Count == 0 ? ValidationResult.Success(RULE_NAME) : ValidationResult.Failure(RULE_NAME, errors); } private static void ValidateRuralClassification( BillComplianceRequest request, GroupSpecificRulesInfo rules, List errors) { if (rules.IsRural) { // Art. 297 - Rural consumer validation if (!rules.SpecialConditions.Any()) { errors.Add("Rural classification requires documented special conditions"); } // Validate activity-specific requirements foreach (var condition in rules.SpecialConditions) { if (!rules.ActivityDiscounts.ContainsKey(condition)) { errors.Add($"Missing discount configuration for rural activity: {condition}"); } } } } private static void ValidateIrrigationRules( BillComplianceRequest request, GroupSpecificRulesInfo rules, List errors) { if (rules.IsIrrigation) { // Art. 298 - Irrigation requirements if (string.IsNullOrEmpty(rules.IrrigationSchedule)) { errors.Add(SCHEDULE_ERROR); } if (!rules.IrrigationDiscount.HasValue) { errors.Add(DISCOUNT_ERROR); } else if (rules.IrrigationDiscount < MIN_IRRIGATION_DISCOUNT || rules.IrrigationDiscount > MAX_IRRIGATION_DISCOUNT) { errors.Add($"Irrigation discount ({rules.IrrigationDiscount:P}) outside allowed range " + $"({MIN_IRRIGATION_DISCOUNT:P}-{MAX_IRRIGATION_DISCOUNT:P})"); } } } private static void ValidateSeasonalRules( BillComplianceRequest request, GroupSpecificRulesInfo rules, List errors) { // Art. 299 - Seasonal variations if (!string.IsNullOrEmpty(rules.Season)) { if (rules.SeasonStartDate == default || rules.SeasonEndDate == default) { errors.Add(SEASON_ERROR); } var currentDate = request.CurrentReadingDate; if (currentDate >= rules.SeasonStartDate && currentDate <= rules.SeasonEndDate) { if (rules.SeasonalMultiplier <= 0) { errors.Add("Invalid seasonal multiplier"); } // Validate if seasonal multiplier was correctly applied var expectedAmount = request.ConsumptionAmount * rules.SeasonalMultiplier; if (Math.Abs(expectedAmount - request.BillTotalBeforeTaxes) > 0.01m) { errors.Add("Seasonal multiplier not correctly applied to consumption amount"); } } } } private static void ValidateActivityDiscounts( BillComplianceRequest request, GroupSpecificRulesInfo rules, List errors) { foreach (var (activity, discount) in rules.ActivityDiscounts) { if (discount <= 0 || discount > 1) { errors.Add($"{ACTIVITY_ERROR}: Invalid discount percentage for {activity}"); } // If this activity applies to the current bill if (request.ApplicableActivities.Contains(activity)) { var expectedDiscount = request.BillTotalBeforeTaxes * discount; if (Math.Abs(expectedDiscount - request.ActivityDiscountAmount) > 0.01m) { errors.Add($"Activity discount for {activity} not correctly applied"); } } } } } }