using System; using System.Threading.Tasks; using System.Text; using Compliance.Domain.Models; using Compliance.DTOs; using Compliance.Infrastructure.Repositories; using System.Linq; using System.Collections.Generic; namespace Compliance.Services.ValidationRules { public class PublicLightingValidationRule(IDistributorRepository distributorRepository) : IValidationRule { private readonly IDistributorRepository _distributorRepository = distributorRepository; private const string RULE_NAME = "Public Lighting Validation"; private const string AMOUNT_ERROR = "Public lighting charge doesn't match the expected value"; private const string EXEMPT_ERROR = "Public lighting charge applied to exempt consumer"; private const decimal TOLERANCE = 0.01m; public int Priority => 8; public async Task ValidateAsync( BillComplianceRequest request, DistributorInformation distributorInfo, Client clientInfo) { var lightingInfo = await _distributorRepository.GetPublicLightingInformationAsync( request.DistributorName, request.Municipality, request.Month) ?? throw new InvalidOperationException("Public lighting information not found"); var isValid = true; var message = new StringBuilder(); // Check municipal legislation compliance if (!lightingInfo.HasValidMunicipalLaw) { if (request.PublicLightingAmount > 0) { isValid = false; message.AppendLine(EXEMPT_ERROR); } return new ValidationResult { IsValid = isValid, RuleName = RULE_NAME, Message = message.ToString() }; } // Validate charge calculation method var expectedCharge = lightingInfo.CalculationType switch { "Fixed" => lightingInfo.FixedAmount, "Percentage" => request.ConsumptionAmount * (lightingInfo.Percentage / 100m), "ConsumptionRange" => CalculateRangeBasedCharge(request.ConsumptionAmount, lightingInfo.Ranges), _ => 0m }; if (Math.Abs(request.PublicLightingAmount - expectedCharge) > TOLERANCE) { isValid = false; message.AppendLine($"{AMOUNT_ERROR}. Expected: {expectedCharge:F2}, Found: {request.PublicLightingAmount:F2}"); } return new ValidationResult { IsValid = isValid, RuleName = RULE_NAME, Message = message.ToString().TrimEnd() }; } private decimal CalculateRangeBasedCharge(decimal consumption, IEnumerable ranges) { var applicableRange = ranges.FirstOrDefault(r => consumption >= r.MinConsumption && consumption <= (r.MaxConsumption ?? decimal.MaxValue)); return applicableRange?.Amount ?? 0m; } } }