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
}