using adas_core.Application.Exceptions; using adas_core.Application.Repositories.Interfaces; using adas_core.Domain.Models.AppSettings; using adas_core.Domain.Models.Filter; using adas_core.Domain.Models.MongoModels; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Driver; using Serilog; using System.Text.RegularExpressions; using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils; namespace adas_core.Infrastructure.Repositories; /// /// Repository implementation for managing Display entities in MongoDB. /// Provides CRUD operations and specialized queries for display devices. /// public class DisplayRepository : MongoRepository, IDisplayRepository { #region Properties private readonly ApiSettings _apiSettings; private readonly ILogger _logger; #endregion #region Constructor /// /// Initializes a new instance of the DisplayRepository. /// /// API settings containing collection names configuration. /// The MongoDB database instance. /// Logger for repository operations. public DisplayRepository( IOptions apiSettings, IMongoDatabase database, ILogger logger) : base(database) { _logger = logger; _apiSettings = apiSettings.Value; } #endregion #region Methods #region Create /// /// Creates the necessary indexes for the Display collection. /// Creates indexes on displayConfigId and unitId fields for improved query performance. /// public override async Task CreateIndexes() { var options = new CreateIndexOptions { Background = true, Unique = false }; var indexes = new List> { new("{ displayConfigId: 1 }", options), new("{ unitId: 1 }", options) }; await MongoUtils.EnsureIndexes(Collection, indexes); } #endregion #region Read /// /// Gets the name of the collection for displays. /// /// The collection name from API settings. public override string GetCollectionName() { return _apiSettings.Displays; } /// /// Retrieves all displays from the database. /// /// A list of all Display entities. public async Task> GetAll() { var result = await Collection.Find(Builders.Filter.Empty).ToListAsync(); return result; } /// /// Retrieves paginated displays with optional filtering. /// Supports filtering by text, unit ID, unit name, and display type. /// /// The pagination and filtering parameters. /// A fluent queryable for Display results. /// Thrown when the text filter exceeds 100 characters. public IFindFluent GetPaginatedDisplays(PaginationFilter filter) { var filterBuilder = Builders.Filter; var sort = Builders.Sort.Ascending("name"); var filters = new List>(); if (filter.FilteredRequest == null) return CreateFindFluent(filters, sort); // Text filter seguro if (!string.IsNullOrWhiteSpace(filter.FilteredRequest.Text)) { var textFilter = filter.FilteredRequest.Text; if (textFilter.Length > 100) throw new BadRequestException("Text filter too long"); var escapedTextFilter = Regex.Escape(textFilter); filters.Add(filterBuilder.Regex( d => d.Name, new BsonRegularExpression(escapedTextFilter, "i") )); } // UnitId if (!string.IsNullOrWhiteSpace(filter.FilteredRequest.UnitId) && ObjectId.TryParse(filter.FilteredRequest.UnitId, out var unitId)) { filters.Add(filterBuilder.Eq(d => d.UnitId, unitId)); } // UnitName fallback else if (!string.IsNullOrWhiteSpace(filter.FilteredRequest.UnitName)) { filters.Add(filterBuilder.Eq(d => d.Unit!.Name, filter.FilteredRequest.UnitName)); } // DisplayType if (filter.FilteredRequest.DisplayType != null) { filters.Add(filterBuilder.Eq(d => d.Type, filter.FilteredRequest.DisplayType)); } return CreateFindFluent(filters, sort); } /// /// Creates a fluent query for paginated displays with combined filters. /// /// List of filter definitions to apply. /// Sort definition for the query results. /// A fluent queryable for Display results. private IFindFluent CreateFindFluent(List> filters, SortDefinition sort) { var combinedFilter = filters.Any() ? Builders.Filter.And(filters) : Builders.Filter.Empty; // Filtra todo si no hay filtros return Collection.Find(combinedFilter).Sort(sort); } /// /// Retrieves all displays associated with a specific point of care. /// /// The PointOfCare to filter by. /// A list of Display entities associated with the point of care. public async Task> GetByPointOfCare(PointOfCare pointOfCare) { var filter = Builders.Filter.AnyEq(x => x.PointOfCareIdList, pointOfCare.Id); var result = await Collection.Find(filter).ToListAsync(); return result; } /// /// Retrieves a display by its name. /// /// The name of the display to retrieve. /// The Display if found; otherwise, null. public async Task GetByName(string name) { var result = await Collection.FindAsync(Builders.Filter.Eq(p => p.Name, name)); return await result.FirstOrDefaultAsync(); } /// /// Retrieves a display by its ID. /// /// The ObjectId of the display to retrieve. /// The Display if found; otherwise, null. public async Task GetById(ObjectId id) { var result = await Collection.FindAsync(Builders.Filter.Eq(p => p.Id, id)); return await result.FirstOrDefaultAsync(); } /// /// Retrieves a display by its ID with the associated display configuration. /// Performs a MongoDB lookup to join the display with its configuration. /// /// The ObjectId of the display to retrieve. /// The Display with DisplayConfig populated if found; otherwise, null. public async Task GetByIdWithConfigDisplay(ObjectId id) { var pipeline = new BsonDocument[] { new("$match", new BsonDocument("_id", id)), new("$lookup", new BsonDocument { { "from", "config_displays" }, { "localField", "displayConfigId" }, // Asumiendo que este es el campo que refiere a config_display { "foreignField", "_id" }, { "as", "DisplayNurse" } }), new("$unwind", new BsonDocument { { "path", "$configDisplay" }, { "preserveNullAndEmptyArrays", true } }) }; var result = await Collection.AggregateAsync(pipeline, new AggregateOptions { AllowDiskUse = true }); return await result.FirstOrDefaultAsync(); } /// /// Retrieves all displays associated with a specific unit. /// /// The ObjectId of the unit. /// A list of Display entities associated with the unit. public async Task> GetByUnitId(ObjectId id) { var filter = Builders.Filter.Eq(p => p.UnitId, id); var result = await Collection.Find(filter).ToListAsync(); return result; } /// /// Counts the number of displays associated with a specific unit. /// /// The ObjectId of the unit. /// The count of displays for the unit, or 0 if an error occurs. /// Logs errors and returns 0 on failure. public async Task CountByUnitId(ObjectId unitId) { try { return await Collection.CountDocumentsAsync(Builders.Filter.Eq(p => p.UnitId, unitId)); } catch (Exception e) { _logger.LogError(e.Message); return 0; } } /// /// Retrieves all displays associated with a specific display configuration. /// /// The ObjectId of the display configuration. /// A list of Display entities using the specified configuration. public async Task> GetByConfigId(ObjectId id) { var result = await Collection.FindAsync(Builders.Filter.Eq(p => p.DisplayConfigId, id)); return result.ToList(); } /// /// Retrieves all displays that use a specific card configuration through their display configuration. /// Performs an aggregation to join Display with DisplayConfig and filter by cardConfigId. /// /// The ObjectId of the card configuration. /// A list of Display entities using the specified card config, or empty list on error. /// Logs errors and returns empty list on failure. public async Task> GetByCardConfigId(ObjectId configId) { try { var aggregate = Collection.Aggregate() // 1. Unimos la colección Display con DisplayConfig .Lookup( _apiSettings.DisplaysConfig, // Nombre de la colección externa "displayConfigId", // Campo local en la colección 'Display' "_id", // Campo en la colección 'DisplayConfig' "displayConfig" // Nombre de la propiedad en la clase C# (debe coincidir) ) // 2. Convertimos el array resultante del lookup en un objeto único .Unwind("displayConfig", new AggregateUnwindOptions { PreserveNullAndEmptyArrays = false // Si no tiene config, no nos interesa }) // 3. Filtramos por la propiedad interna del objeto ya "unido" // Nota: Usamos el nombre del campo tal cual está en el BSON (normalmente camelCase) .Match(Builders.Filter.Eq("displayConfig.cardConfigId", configId)) // 4. Casteamos el resultado de vuelta a nuestra clase Display .As(); return await aggregate.ToListAsync(); } catch (Exception ex) { _logger.LogError("Error en GetByCardConfigId: {ex}", ex.Message); return []; } } /// /// Checks if a display configuration is currently in use by any displays. /// /// The ObjectId of the display configuration to check. /// The count of displays using the configuration. public async Task IsDisplayConfigInUse(ObjectId displayConfigId) { return await Collection.CountDocumentsAsync( Builders.Filter.Eq(p => p.DisplayConfigId, displayConfigId)); } #endregion #region Update /// /// Updates the point of care list for a specific display. /// /// The ObjectId of the display to update. /// The new list of point of care ObjectIds. /// The updated Display if successful; otherwise, null. /// Logs errors and returns null on failure. public async Task UpdatePointOfCareList(ObjectId objectId, List listPocObId) { try { var filter = Builders.Filter.Eq("Id", objectId); var update = Builders.Update .Set(c => c.PointOfCareIdList, listPocObId); return await Collection.FindOneAndUpdateAsync(filter, update, new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After }); } catch (Exception e) { _logger.LogError($"Unable to update pointOfCareList from Display Exception {e}"); return null; } } /// /// Updates the display configuration for a specific display. /// /// The ObjectId of the display to update. /// The new DisplayConfig to set. /// The updated Display if successful; otherwise, null. /// Logs errors and returns null on failure. public async Task UpdateConfig(ObjectId objectId, DisplayConfig config) { try { var filter = Builders.Filter.Eq("Id", objectId); var update = Builders.Update .Set(c => c.DisplayConfig, config); return await Collection.FindOneAndUpdateAsync(filter, update, new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After }); } catch (Exception e) { _logger.LogError($"Unable to update config from Display Exception {e.Message}"); return null; } } /// /// Updates the display configuration ID reference for a specific display. /// /// The ObjectId of the display to update. /// The new display configuration ObjectId to set. /// The updated Display if successful; otherwise, null. /// Logs errors and returns null on failure. public async Task UpdateConfigId(ObjectId objectId, ObjectId displayConfigId) { try { var filter = Builders.Filter.Eq("Id", objectId); var update = Builders.Update .Set(c => c.DisplayConfigId, displayConfigId); return await Collection.FindOneAndUpdateAsync(filter, update, new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After }); } catch (Exception e) { _logger.LogError($"Unable to update config from Display Exception {e.Message}"); return null; } } /// /// Updates the display configuration preset for a specific display. /// /// The ObjectId of the display to update. /// The ObjectId of the display configuration preset. /// The updated Display if successful; otherwise, null. /// Logs errors and returns null on failure. public async Task UpdateConfigPreset(ObjectId objectIdDisplay, ObjectId objectIdConfigDisplay) { try { var filter = Builders.Filter.Eq("Id", objectIdDisplay); var update = Builders.Update .Set(c => c.DisplayConfigId, objectIdConfigDisplay); return await Collection.FindOneAndUpdateAsync(filter, update, new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After }); } catch (Exception e) { _logger.LogError($"Unable to update config from Display Exception: {e.Message}"); return null; } } /// /// Updates the name of a specific display. /// /// The Display entity to update. /// The new name for the display. /// The updated Display if successful. /// Throws an exception if MongoDB update fails. public async Task UpdateName(Display display, string name) { try { var filter = Builders.Filter.Eq("_id", display.Id); var update = Builders.Update .Set(d => d.Name, name); return await Collection.FindOneAndUpdateAsync(filter, update, new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After }); } catch (Exception ex) { _logger.LogError(ex.Message); throw; } } #endregion #region Delete /// /// Deletes all displays associated with a specific unit. /// /// The ObjectId of the unit whose displays should be deleted. /// True if deletion was successful; otherwise, false. /// Logs errors and returns false on failure. public async Task DeleteManyByUnitId(ObjectId unitId) { try { var filter = Builders.Filter.Where(p => p.UnitId == unitId); await Collection.DeleteManyAsync(filter); return true; } catch (Exception e) { Log.Logger.Error(e.Message); return false; } } #endregion #endregion }