using adas_core.Application.Exceptions; using adas_core.Application.Repositories.Interfaces; using adas_core.Application.Services.Interfaces; using adas_core.Application.Subscriptions; using adas_core.Domain.Enums; using adas_core.Domain.Models.AppSettings; using adas_core.Domain.Models.DTO; using adas_core.Domain.Models.DTO.Display; using adas_core.Domain.Models.Filter; using adas_core.Domain.Models.Masters; using adas_core.Domain.Models.MongoModels; using adas_core.Domain.Models.Responses; using adas_core.Domain.Utils; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Driver; namespace adas_core.Application.Services; public class DisplayService( IDisplayRepository displayRepository, IPointOfCareService pointOfCareService, Lazy unitService, IDisplayConfigService displayConfigService, ISubscribersService subscribersService, IClientMessageService clientMessageService, IUserRepository userRepository, IAuthService authorityService, ILogger logger, IHttpContextAccessor httpContextAccessor, ILocalAuditService auditService, Lazy permissionService, ICacheService cacheService, IOptions cacheSettings) : IDisplayService { private readonly CacheSettings? _cacheSettings = cacheSettings.Value ; #region Methods #region Create public async Task InsertOne(Display display) { var defaultConfig = await displayConfigService.GetDefaultConfig(display.Type) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); display.DisplayConfigId = defaultConfig.Id; await displayRepository.InsertOneAsync(display); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, display); return display; } public async Task InsertOneTest() { var d = new Display { Name = "DisplayTEST", UnitId = new ObjectId("65ba5f89d5ba8e273cf9cd96"), DisplayConfigId = new ObjectId("45ba5f89d5ba8e273cf9cd96") }; await displayRepository.InsertOneAsync(d); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, d); return d; } #endregion #region Read public async Task> GetAllCompact() { var result = await displayRepository.GetAll(); List listToReturn = []; foreach (var res in result) listToReturn.Add(new DisplayMinimalDto(res)); return listToReturn; } public Task> GetAll(string? userName) { throw new NotImplementedException(); } public async Task> GetPaginatedDisplays(PaginationFilter filter) { var result = displayRepository.GetPaginatedDisplays(filter); var count = await result.CountDocumentsAsync(); var data = await result.Skip((filter.PageNumber - 1) * filter.PageSize) .Limit(filter.PageSize) .ToCursorAsync(); var dataList = await data.ToListAsync(); return new PaginationResponse(dataList, filter.PageNumber, filter.PageSize, count); } public async Task> GetAllByUser(string? userName) { var start = DateTime.Now; var listToReturn = new List(); if (userName == null) return listToReturn; var user = await userRepository.GetByUserAndAuthoritesName(userName); if (user == null) return listToReturn; if (user.Authorization == null || user.Authorization.Count == 0) user.Authorization = await authorityService.GetUserAuthorities(user.Id); if (user.Authorization == null) return listToReturn; foreach (var e in user.Authorization) if (e.UnitId != null) { var isParsed = ObjectId.TryParse(e.UnitId, out var dId); if (isParsed) { var dis = await displayRepository.GetByUnitId(dId); foreach (var display in dis) { var toAdd = await GetInfo(display.Id, userName, user.Authorization,null, false, false, false, false); if (toAdd != null) { var newDto = new DisplayWithPermissionsDto { Display = toAdd, Permissions = await permissionService.Value.GetPermissionsForUnit(dId.ToString(), user) ?? throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission) }; listToReturn.Add(newDto); } } } } else if (e.DisplayId != null && !FindDisplayInPerms(e.DisplayId, listToReturn)) { var isParsed = ObjectId.TryParse(e.DisplayId, out var dId); if (isParsed) { var toAdd = await GetInfo(dId, userName, user.Authorization, null, false, false, false, false); if (toAdd != null) { var newDto = new DisplayWithPermissionsDto { Display = toAdd, Permissions = await permissionService.Value.GetPermissionsForDisplay(toAdd, user) }; listToReturn.Add(newDto); } } } var end = DateTime.Now; logger.LogDebug("Finished GetAllByUser Displays for user {user} in {TotalSeconds:F1} seconds", userName, (end - start).TotalSeconds); return listToReturn; } private static bool FindDisplayInPerms(string displayId, List perms) { return perms.Any(p => p.Display != null && p.Display.Id.ToString() == displayId); } public async Task> GetByType(DisplayConfigEnums.DisplayType type) { var configs = await displayConfigService.GetByType(type); var listToReturn = new List(); foreach (var config in configs) { var displayToAdd = await displayRepository.GetByConfigId(config.Id); displayToAdd.ForEach(c => c.DisplayConfig = config); if (!displayToAdd.IsNullOrEmpty()) listToReturn.AddRange(displayToAdd); } return listToReturn; } public async Task> GetByPointOfCare(PointOfCare pointOfCare) { return await displayRepository.GetByPointOfCare(pointOfCare); } public Task> GetByConfigId(ObjectId configId) { return displayRepository.GetByConfigId(configId); } public Task> GetByCardConfigId(ObjectId configId) { return displayRepository.GetByCardConfigId(configId); } public async Task GetByName(string name) { return await displayRepository.GetByName(name) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); } public async Task GetById(ObjectId id) { return await displayRepository.GetById(id); } public async Task GetByIdWithPermissions(ObjectId id, LocaleEnum localeEnum) { var username = JwtHelper.GetUsernameFromPrincipal(httpContextAccessor.HttpContext?.User!) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); var user = await userRepository.GetByUserName(username); var display = await GetById(id); if (display == null) throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); foreach (var poc in display.PointOfCareIdList) { var c = await pointOfCareService.GetInfo(poc, localeEnum); if (c != null) display.PointOfCares.Add(c); } display.DisplayConfig = await displayConfigService.GetById(display.DisplayConfigId, display.UnitId, display.Type); display.DisplayConfig!.DisplaySectionList = []; try { display.DisplayConfig!.DisplaySectionList = await GetDisplaySectionByUser(display.Type, id, username, user?.Authorization); } catch (Exception e) { Console.WriteLine(e); throw; } return new DisplayWithPermissionsDto { Display = display, Permissions = await permissionService.Value.GetPermissionsForDisplay(display, user!) }; } public async Task CountDisplaysByUnitId(ObjectId unitId) { return await displayRepository.CountByUnitId(unitId); } public async Task GetInfo(ObjectId id, string? userName, List? authorizations, LocaleEnum? locale, bool fillPointOfCare = true, bool fillPatientData = false, bool fillDisplayList = true, bool fillDisplayConfig = true, CancellationToken ct = default) { var start = DateTime.Now; Display? display; if (fillDisplayConfig) { // Clave: display con configuración var (key, ttl) = CacheKeys.DisplayWithConfigKeyWithTtl(_cacheSettings, id); display = await cacheService.GetOrSetObjectAsync( key, async () => await BuildDisplayWithConfig(id, ct), ttl, ct); } else { // Clave: display base var (key, ttl) = CacheKeys.DisplayBaseKeyWithTtl(_cacheSettings, id); display = await cacheService.GetOrSetObjectAsync( key, async () => await displayRepository.GetById(id), ttl, ct); } if (display == null) return null; // PointOfCare (cacheado en su propio servicio) if (fillPointOfCare) foreach (var poc in display.PointOfCareIdList) { var c = await pointOfCareService.GetInfo(poc, locale, fillPatientData); if (c != null) display.PointOfCares.Add(c); } if (authorizations == null && userName != null) { var c = await userRepository.GetByUserAndAuthoritesName(userName); authorizations = c?.Authorization; } // DisplayList (depende de autorizaciones → NO cacheable) if (fillDisplayList) if (display.DisplayConfig != null) display.DisplayConfig.DisplaySectionList = await GetDisplaySectionByUser(display.Type, id, userName, authorizations); else logger.LogError("DISPLAY CONFIG NULL on fill display list"); var end = DateTime.Now; logger.LogDebug("Finished GetInfo Displays for user {user} in {TotalSeconds:F1} seconds", userName, (end - start).TotalSeconds); return display; } private async Task BuildDisplayWithConfig(ObjectId id, CancellationToken ct) { var display = await displayRepository.GetById(id); if (display == null) return null; var type = display.Type; // DisplayConfig display.DisplayConfig = await displayConfigService.GetById(display.DisplayConfigId, display.UnitId, type); if (type != DisplayConfigEnums.DisplayType.SmartDisplay || display.DisplayConfig is not SmartDisplay { CardRotatingLayout: not null } smart) return display; if(smart.CardRotatingLayout== null) return display; foreach (var card in smart.CardRotatingLayout) card.Data = await displayConfigService.GetCardConfigById(card.DataId) ?? new CardConfig(); return display; } public async Task> GetDisplaySectionByUser( DisplayConfigEnums.DisplayType type, ObjectId? currentDisplay, string? userName, List? authorizations) { try { var listToReturn = new List(); if (userName != null) { var user = await userRepository.GetByUserName(userName); if (user != null) { var displayIdByAuthorities = authorizations ?? await authorityService.GetUserAuthorities(user.Id); foreach (var e in displayIdByAuthorities) { var isParsed = ObjectId.TryParse(e.DisplayId, out var dId); if (isParsed) { if (listToReturn.All(c => c.Id != dId)) { var toAdd = await GetById(dId); if (toAdd != null) if (type == toAdd.Type) { var minDisSec = new MinimalDisplaySection { Name = toAdd.Name, Id = toAdd.Id, IsSelected = currentDisplay != null && toAdd.Id == currentDisplay }; listToReturn.Add(minDisSec); } } } else { var isParsedUnitId = ObjectId.TryParse(e.UnitId, out var uId); if (isParsedUnitId) { var unitDisplays = await GetByUnitId(uId); foreach (var disp in unitDisplays) if (type == disp.Type && listToReturn.All(c => c.Id != dId)) { var minDisSec = new MinimalDisplaySection { Name = disp.Name, Id = disp.Id, IsSelected = currentDisplay != null && disp.Id == currentDisplay }; listToReturn.Add(minDisSec); } } } } } } return listToReturn; } catch (Exception e) { Console.WriteLine(e); throw; } } public async Task GetAllDisplaySection() { var minimalDisplayListDto = new MinimalDisplayListDto(); var listDisplayNurse = await GetByType(DisplayConfigEnums.DisplayType.DisplayNurse); foreach (var displayForAdmin in listDisplayNurse) { var minDisSec = new MinimalDisplaySection { Name = displayForAdmin.Name, Id = displayForAdmin.Id }; minimalDisplayListDto.DisplayNurse.Add(minDisSec); } var listDisplaySmart = await GetByType(DisplayConfigEnums.DisplayType.SmartDisplay); foreach (var displayForAdmin in listDisplaySmart) { var minDisSec = new MinimalDisplaySection { Name = displayForAdmin.Name, Id = displayForAdmin.Id }; minimalDisplayListDto.SmartDisplay.Add(minDisSec); } return minimalDisplayListDto; } public async Task> GetByUnitId(ObjectId unitId) { return await displayRepository.GetByUnitId(unitId); } public async Task GetAllAvailablePoc(List displayIds, bool excludeVirtual = false) { try { var listToReturn = new PocAndUnitDto(); var listObjectId = new List(); foreach (var displayId in displayIds) { var isParsed = ObjectId.TryParse(displayId, out var dId); if (isParsed) { var diplay = await displayRepository.GetById(dId); if (diplay != null) listObjectId.Add(diplay.UnitId); } } var distinctObjectIds = listObjectId.Distinct().ToList(); foreach (var distinctObjectId in distinctObjectIds) { var unit = await unitService.Value.FindById(distinctObjectId) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); var pocList = await pointOfCareService.FindByUnitAndStatus(distinctObjectId, StatusEnum.PointOfCare.Available, excludeVirtual); foreach (var pointOfCare in pocList) { var pocAv = new MinimalPocAndUnitDto { PocId = pointOfCare.Id, PocName = pointOfCare.Bed, UnitId = pointOfCare.UnitId, UnitName = unit.Name }; listToReturn.PocList.Add(pocAv); } } return listToReturn; } catch (Exception e) { logger.LogError("Error GetAllAvailablePoc {Error}", e.Message); return new PocAndUnitDto(); } } public async Task> GetAllPocsByDisplayId(ObjectId id) { try { var pocList = new List(); var display = await displayRepository.GetById(id); if (display == null) return pocList; foreach (var pocId in display.PointOfCareIdList) { var poc = await pointOfCareService.FindById(pocId); if (poc != null) pocList.Add(poc); } return pocList; } catch (Exception e) { logger.LogError("Error GetAllAvailablePoc {Error}", e.Message); return []; } } public async Task> GetDisplayConfigLocations(ObjectId displayConfigId) { var displays = await GetByConfigId(displayConfigId); var locations = new List(); foreach (var display in displays) { var unit = await unitService.Value.FindById(display.UnitId); DisplayConfigLocationDto newLocation = new() { DisplayName = display.Name, UnitName = unit?.Name, }; locations.Add(newLocation); } return locations; } public async Task IsDisplayConfigInUse(ObjectId displayConfigId) { return await displayRepository.IsDisplayConfigInUse(displayConfigId) > 0; } #endregion #region Update /* * En esta actualización se espera una resubscipción al id del display ya que actualizar los PoC conlleva actualizar * subscrioptor y locations para las observaciones */ public async Task UpdatePointOfCareList(ObjectId objectId, List listPocObId) { var oldDisplay = await displayRepository.GetById(objectId) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); var displayToReturn = await displayRepository.UpdatePointOfCareList(objectId, listPocObId) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed); await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(objectId)); SendDisplayBroadcast(displayToReturn, OperationType.UpdateDisplayPoC); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplay, displayToReturn); return displayToReturn; } public async Task UpdateConfig(Display oldDisplay, DisplayConfig? newDisplayConfig) { var newDisplayConfigCast = new DisplayConfig(); switch (newDisplayConfig?.Type) { case DisplayConfigEnums.DisplayType.DisplayNurse: newDisplayConfigCast = newDisplayConfig as DisplayNurse; break; case DisplayConfigEnums.DisplayType.SmartDisplay: newDisplayConfigCast = newDisplayConfig as SmartDisplay; break; } if (newDisplayConfigCast != null) { var displayToReturn = await displayRepository.UpdateConfig(oldDisplay.Id, newDisplayConfigCast); if (displayToReturn != null) { SendDisplayBroadcast(displayToReturn, OperationType.UpdateDisplayConfig); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplay, displayToReturn); } await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(oldDisplay.Id)); return displayToReturn; } return null; } public async Task UpdateConfigId(Display oldDisplay, ObjectId configId) { var displayToReturn = await displayRepository.UpdateConfigId(oldDisplay.Id, configId); if (displayToReturn != null) { await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(oldDisplay.Id)); SendDisplayBroadcast(displayToReturn, OperationType.UpdateDisplayConfig); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplay, displayToReturn); } return displayToReturn; } public async Task UpdateConfigPreset(ObjectId objectIdDisplay, ObjectId objectIdConfigDisplay) { var oldConfig = await displayConfigService.GetById(objectIdConfigDisplay); var result = await displayRepository.UpdateConfigPreset(objectIdDisplay, objectIdConfigDisplay); await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(objectIdDisplay)); var config = await displayConfigService.GetById(objectIdConfigDisplay); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldConfig, config); if (result == null || config == null) throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); var subscribers = subscribersService.GetSubscribers().Where(s => s.DisplayId == objectIdDisplay).ToList(); switch (config.Type) { case DisplayConfigEnums.DisplayType.DisplayNurse: SendNurseDisplayBroadcast(subscribers, config as DisplayNurse); break; case DisplayConfigEnums.DisplayType.SmartDisplay: SendSmartDisplayBroadcast(subscribers, config as SmartDisplay); break; case DisplayConfigEnums.DisplayType.Unknown: break; default: throw new InvalidFormatException(HttpEnum.ErrorMessage.BadRequestIncorrectType); } return result; } public async Task UpdateName(ObjectId id, string name) { var display = await displayRepository.GetById(id) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); var newDisplay = await displayRepository.UpdateName(display, name); await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(id)); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, display, newDisplay); return newDisplay; } #endregion #region Delete public async Task DeleteDisplay(ObjectId id) { var display = await displayRepository.GetById(id) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); await displayRepository.DeleteAsync(id); // Invalidar CACHE (colección completa) await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(id)); await authorityService.DeleteByDisplayId(id); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, display, null); return true; } public async Task DeleteDisplaysByUnitId(ObjectId unitId) { await displayRepository.DeleteManyByUnitId(unitId); // Invalidar CACHE (colección completa) await cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.Displays)); await authorityService.DeleteByUnitId(unitId); } #endregion #region Send Notification private void SendSmartDisplayBroadcast(List subscribers, SmartDisplay? config) { foreach (var subscriber in subscribers) _ = clientMessageService.SendAsync(subscriber.Id, OperationType.UpdateSmartDisplayConfig, config); } private void SendNurseDisplayBroadcast(List subscribers, DisplayNurse? config) { foreach (var subscriber in subscribers) _ = clientMessageService.SendAsync(subscriber.Id, OperationType.UpdateNurseDisplayConfig, config); } private void SendDisplayBroadcast(Display display, OperationType operation) { var subscribers = subscribersService.GetSubscribers().Where(s => s.DisplayId == display.Id).ToList(); switch (operation) { case OperationType.UpdateDisplayPoC: foreach (var subscriber in subscribers) _ = clientMessageService.SendAsync(subscriber.Id, operation, null); break; } } #endregion #endregion }