using LCU.Library.Internal.IncentiveApiModels.Entities; using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; using LCU.Library.Internal.IncentiveApiModels.Enums; using LCU.Library.Internal.IncentiveApiModels.Common; namespace LCU.API.Internal.Operations.IncentiveApi.Services; public class GapInsuranceActivityService : IGapInsuranceActivityService { private readonly IActivityDetailService _activityDetailService; private readonly IActivityRepository _activityRepository; private readonly IActivityService _activityService; private readonly IUnitOfWork _unitOfWork; public GapInsuranceActivityService( IActivityDetailService activityDetailService, IActivityRepository activityRepository, IActivityService activityService, IUnitOfWork unitOfWork ) { _activityDetailService = activityDetailService; _activityRepository = activityRepository; _activityService = activityService; _unitOfWork = unitOfWork; } public async Task MatchOrRemoveGapAccessories(MonthRange monthRange = null) { var updateCount = 0; monthRange ??= new MonthRange(DateTime.UtcNow.AddMonths(-3), DateTime.UtcNow); var gapEligibleActivities = await _activityRepository.FindGapInsuranceEligible(monthRange); if (!gapEligibleActivities.Any()) { return updateCount; } var matchedGapEligibleActivities = gapEligibleActivities.Where(x => x.HasActiveGapAccessory()).ToList(); var matchedGapAccessoryActivities = await GetMatchingGapAccessoryActivities(matchedGapEligibleActivities); matchedGapEligibleActivities = await ValidateMatchedActivityAccessories(matchedGapEligibleActivities, matchedGapAccessoryActivities); var unmatchedGapEligibleActivities = gapEligibleActivities.Except(matchedGapEligibleActivities).ToList(); var gapActivities = await _activityRepository.GetFilteredActivities(monthRange, Product.GAPInsurance, ActivityState.Accepted); foreach (var activity in unmatchedGapEligibleActivities) { try { updateCount += await TryMatchGapEligibleActivityWithAccessory(activity, gapActivities) != null ? 2 : 0; } catch (Exception error) { Log.Error(error, $"MatchOrRemoveGapAccessories failed processing Activity Id: {activity.Id}"); } } return updateCount; } private async Task TryMatchGapEligibleActivityWithAccessory(Activity gapEligibleActivity, List gapActivities) { if (gapActivities.Count <= 0) { return null; } var matchingGapAccessories = gapActivities .Where(x => x.Detail.AccountNumber.Equals(gapEligibleActivity.Detail.AccountNumber, StringComparison.CurrentCultureIgnoreCase)) .ToList(); if (matchingGapAccessories.Count <= 0) { return null; } if (matchingGapAccessories.Count > 1) { throw new Exception( $"Multiple matching GAP Accessory Activities found for Activity Id: {gapEligibleActivity.Id} using AccountNumber: {gapEligibleActivity.Detail.AccountNumber}"); } var matchedAccessoryActivity = matchingGapAccessories.First(); return await MatchGapAccessory(gapEligibleActivity, matchedAccessoryActivity); } private async Task MatchGapAccessory(Activity parentActivity, Activity accessoryActivity) { var addedDateTime = accessoryActivity.Accepted.Date.AddHours(12); await ApplyMatchedChangeUpdates(parentActivity, accessoryActivity, addedDateTime); await ApplyMatchedChangeUpdates(accessoryActivity, parentActivity, addedDateTime); await _unitOfWork.CompleteAsync(); return parentActivity; } private async Task ApplyMatchedChangeUpdates(Activity targetActivity, Activity linkedActivity, DateTime addedDateTime) { targetActivity.Detail.GapInsuranceAddDate = addedDateTime; targetActivity.MatchingGapInsuranceActivityId = linkedActivity.Id; await _activityDetailService.SaveActivityDetailUpdate(new ActivityDetailUpdate { ActivityDetailId = targetActivity.Detail.Id, Note = $"GapInsuranceAddDate has changed from NULL to {addedDateTime}" }); if (targetActivity.Detail.GapInsuranceRemoveDate != null) { targetActivity.Detail.GapInsuranceRemoveDate = null; await _activityDetailService.SaveActivityDetailUpdate(new ActivityDetailUpdate { ActivityDetailId = targetActivity.Detail.Id, Note = $"GapInsuranceRemoveDate has changed from {targetActivity.Detail.GapInsuranceRemoveDate} to NULL" }); } await _activityService.ApplyStateChange(new ActivityStateChange { ActivityId = targetActivity.Id, ActivityMatchId = linkedActivity.Id, Note = $"Matched {linkedActivity.Product} Activity Id: {linkedActivity.Id} Added", State = targetActivity.State }, false); } private async Task> ValidateMatchedActivityAccessories(List matchedActivities, List accessoryActivities) { var accessoryRemovedActivities = new List(); foreach (var parentActivity in matchedActivities) { try { var accessoryActivity = accessoryActivities.FirstOrDefault(x => x.MatchingGapInsuranceActivityId == parentActivity.Id) == null ? await _activityRepository.GetActivityById(parentActivity.MatchingGapInsuranceActivityId) : accessoryActivities.FirstOrDefault(x => x.MatchingGapInsuranceActivityId == parentActivity.Id); if (accessoryActivity == null && parentActivity.Detail.GapInsuranceRemoveDate == null) { accessoryRemovedActivities.Add(await RemoveMissingGapAccessory(parentActivity)); continue; } if (parentActivity?.State == ActivityState.Accepted && accessoryActivity?.State == ActivityState.Accepted) { continue; } accessoryRemovedActivities.Add(await RemoveGapAccessory(parentActivity, accessoryActivity)); } catch (Exception ex) { var parentActivityId = parentActivity != null ? parentActivity.Id.ToString() : "NULL"; Log.Error(ex, $"Failed to validate matched GAP accessory for Activity Id:{parentActivityId}"); } } Log.Information($"Removed GAP Accessory From {accessoryRemovedActivities.Count} Activities"); return matchedActivities.Except(accessoryRemovedActivities).ToList(); } private async Task RemoveGapAccessory(Activity parentActivity, Activity accessoryActivity) { var removedDate = await DetermineRemovedDate(parentActivity, accessoryActivity); await ApplyRemovalChangeUpdates(parentActivity, accessoryActivity, removedDate); await ApplyRemovalChangeUpdates(accessoryActivity, parentActivity, removedDate); await _unitOfWork.CompleteAsync(); return parentActivity.State == ActivityState.Accepted ? parentActivity : null; } private async Task ApplyRemovalChangeUpdates(Activity targetActivity, Activity linkedActivity, DateTime removedDateTime) { targetActivity.Detail.GapInsuranceRemoveDate = removedDateTime; targetActivity.MatchingGapInsuranceActivityId = 0; await _activityDetailService.SaveActivityDetailUpdate(new ActivityDetailUpdate { ActivityDetailId = targetActivity.Detail.Id, Note = $"GapInsuranceRemoveDate has changed from NULL to {removedDateTime}" }); await _activityService.ApplyStateChange(new ActivityStateChange { ActivityId = targetActivity.Id, ActivityMatchId = linkedActivity.Id, Note = $"Matched {linkedActivity.Product} Activity Id: {linkedActivity.Id} Removed", State = targetActivity.State }, false); } private static async Task DetermineRemovedDate(Activity parentActivity, Activity accessoryActivity) { if (parentActivity.State == ActivityState.Accepted && accessoryActivity.State != ActivityState.Accepted) { return await Task.FromResult(accessoryActivity.Accepted.Date.AddHours(12)); } if (parentActivity.State != ActivityState.Accepted && accessoryActivity.State == ActivityState.Accepted) { return await Task.FromResult(parentActivity.Accepted.Date.AddHours(12)); } return await Task.FromResult(parentActivity.LastUpdated <= accessoryActivity.LastUpdated ? parentActivity.Accepted.Date.AddHours(12) : accessoryActivity.Accepted.Date.AddHours(12)); } private async Task RemoveMissingGapAccessory(Activity activity) { Log.Warning($"Unable To Find GAP Accessory Activity Id: {activity.MatchingGapInsuranceActivityId}, Removing GAP Accessory From Activity Id: {activity.Id}"); var removeDate = DateTime.Now.Date.AddHours(12); activity.MatchingGapInsuranceActivityId = 0; activity.Detail.GapInsuranceRemoveDate = removeDate; await _activityDetailService.SaveActivityDetailUpdate(new ActivityDetailUpdate { ActivityDetailId = activity.Detail.Id, Note = $"GapInsuranceRemoveDate has changed from NULL to {removeDate}" }); var activityStateChange = new ActivityStateChange { ActivityId = activity.Id, Note = $"{Product.GAPInsurance} Accessory Activity Removed, Accessory Activity Not Found", State = activity.State }; await _activityService.ApplyStateChange(activityStateChange); return activity.State == ActivityState.Accepted ? activity : null; } private async Task> GetMatchingGapAccessoryActivities(IReadOnlyCollection matchedActivities) { if (!matchedActivities.Any()) { return new List(); } var ids = matchedActivities .Select(activity => activity.MatchingGapInsuranceActivityId) .ToList(); return await _activityRepository.FindById(ids); } }