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 FlagTariffValidationRule(IDistributorRepository distributorRepository) : IValidationRule { private readonly IDistributorRepository _distributorRepository = distributorRepository; private const string RULE_NAME = "Flag Tariff Validation"; private const string FLAG_ERROR = "Flag tariff amount doesn't match the expected value for the current flag color"; private const string EXEMPT_ERROR = "Flag tariff charged to exempt consumer"; private const string COLOR_ERROR = "Invalid flag color applied"; private const decimal TOLERANCE = 0.01m; private const decimal KWH_BLOCK = 100m; // Flag tariff is applied per 100 kWh public int Priority => 4; // Run after tariff component validation public async Task ValidateAsync( BillComplianceRequest request, DistributorInformation distributorInfo, Client clientInfo) { var flagInfo = await _distributorRepository.GetFlagTariffInformationAsync( request.DistributorName, request.Month) ?? throw new InvalidOperationException("Flag tariff information not found"); var isValid = true; var message = new StringBuilder(); // Check if consumer is exempt if (flagInfo.ExemptGroups.Contains(request.ConsumerGroup.ToLower())) { if (request.FlagAmount != 0) { isValid = false; message.AppendLine(EXEMPT_ERROR); } return new ValidationResult { IsValid = isValid, RuleName = RULE_NAME, Message = message.ToString().TrimEnd() }; } // Validate flag color if (!IsValidFlagColor(request.AppliedFlag)) { isValid = false; message.AppendLine($"{COLOR_ERROR}. Found: {request.AppliedFlag}"); } // Calculate expected flag amount var expectedFlagAmount = CalculateFlagAmount( request.ConsumptionAmount, flagInfo.FlagColor, flagInfo.FlagValue, request.BillingDays, flagInfo.ApplyPartialMonth, flagInfo.ValidFromDay); if (Math.Abs(request.FlagAmount - expectedFlagAmount) > TOLERANCE) { isValid = false; message.AppendLine($"{FLAG_ERROR}. Expected: {expectedFlagAmount:F2}, Found: {request.FlagAmount:F2}"); } return new ValidationResult { IsValid = isValid, RuleName = RULE_NAME, Message = message.ToString().TrimEnd() }; } private static bool IsValidFlagColor(string flagColor) { return flagColor.ToUpper() switch { "GREEN" => true, "YELLOW" => true, "RED1" => true, "RED2" => true, _ => false }; } private static decimal CalculateFlagAmount( decimal consumption, string flagColor, decimal flagValue, int billingDays, bool applyPartialMonth, int validFromDay) { if (flagColor.Equals("GREEN", StringComparison.CurrentCultureIgnoreCase)) return 0; // Calculate number of 100 kWh blocks var blocks = Math.Ceiling(consumption / KWH_BLOCK); // If partial month application is enabled, prorate the flag value if (applyPartialMonth) { var applicableDays = billingDays - (validFromDay - 1); if (applicableDays < billingDays) { return blocks * flagValue * (applicableDays / (decimal)billingDays); } } return blocks * flagValue; } } }