using adas_core.Application.Repositories.Interfaces;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;

namespace adas_core.Infrastructure.Repositories;

/// <summary>
/// Repository for managing patient observation alarms in MongoDB. Provides methods to retrieve aggregated patient observations based on specified fields and expiration status. 
/// Implements the IAlarmRepository interface and extends the MongoRepository base class for common MongoDB operations.
/// </summary>
public class AlarmRepository : MongoRepository<PatientObservationAlarm>, IAlarmRepository
{
    /// <summary>
    /// API settings containing configuration for the MongoDB collection name and other relevant settings. Injected via constructor and used to determine the collection name for patient observation alarms.
    /// </summary>
    private readonly ApiSettings _apiSettings;

    /// <summary>
    /// Logger instance for logging errors and information related to the AlarmRepository operations. Injected via constructor and used throughout the repository methods to log exceptions and important events.
    /// </summary>
    private readonly ILogger<AlarmRepository> _logger;

    /// <summary>
    /// Constructor for the AlarmRepository class. Initializes the repository with the provided API settings, MongoDB database instance, and logger. 
    /// Validates the input parameters and sets up the necessary configurations for accessing the patient observation alarms collection in MongoDB.
    /// </summary>
    /// <param name="apiSettings">The API settings containing configuration for the MongoDB collection name and other relevant settings.</param>
    /// <param name="database">The MongoDB database instance.</param>
    /// <param name="logger">The logger instance for logging errors and information.</param>
    /// <exception cref="ArgumentNullException">Thrown when any of the input parameters are null.</exception>
    public AlarmRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database, ILogger<AlarmRepository> logger)
        : base(database)
    {
        if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
        _logger = logger;
        _apiSettings = apiSettings.Value;
    } //For testing


    /// <summary>
    /// Retrieves a list of patient observation alarms for a specific patient based on the provided filter criteria. 
    /// The method allows filtering observations by name and expiration status, and returns the most recent observations for each specified field. If no filter is provided, it retrieves all observations for the patient sorted by time in descending order.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient.</param>
    /// <param name="filterObservations">A list of fields to filter the observations. If null, all observations for the patient are retrieved.</param>
    /// <returns>A list of patient observation alarms matching the filter criteria.</returns>
    public async Task<List<PatientObservationAlarm>> AggregatedPatientLastObservationsByField(ObjectId patientId,
        List<Field>? filterObservations = null)
    {
        try
        {
            var results = new List<PatientObservationAlarm>();
            IAsyncCursor<PatientObservationAlarm>? cursor;
            var builder = Builders<PatientObservationAlarm>.Filter;

            if (filterObservations != null)
            {
                foreach (var obs in filterObservations)
                {
                    FilterDefinition<PatientObservationAlarm> filter;

                    if (obs is { OnlyExpired: true, Name: not null })
                        filter = builder.And(
                            builder.Eq(o => o.PatientId, patientId),
                            builder.Eq(o => o.Name, obs.Name),
                            builder.Eq("Expired", obs.OnlyExpired)
                            //builder.Eq(o => o.Expired, obs.OnlyExpired)
                        );
                    else
                        filter = builder.And(
                            builder.Eq(o => o.PatientId, patientId),
                            builder.Eq(o => o.Name, obs.Name)
                        );

                    cursor = await Collection.FindAsync(
                        filter,
                        new FindOptions<PatientObservationAlarm>
                        {
                            Sort = Builders<PatientObservationAlarm>.Sort.Descending("time").Descending("id"),
                            Limit = obs.Last
                        });

                    results.AddRange(cursor.ToEnumerable());
                }
            }
            else
            {
                var filter = builder.Eq(o => o.PatientId, patientId);

                cursor = await Collection.FindAsync(
                    filter,
                    new FindOptions<PatientObservationAlarm>
                        { Sort = Builders<PatientObservationAlarm>.Sort.Descending("time").Descending("id") });

                results.AddRange(cursor.ToEnumerable());
            }

            return results;
        }
        catch (Exception ex)
        {
            _logger.LogError("Error aggregated patient last observations by field {exMessage}", ex.Message);
            throw;
        }
    }

    /// <summary>
    /// Retrieves a list of patient observation alarms for a specific patient based on the provided filter criteria, including expiration status.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient.</param>
    /// <param name="filterObservations">A list of fields to filter the observations. If null, all observations for the patient are retrieved.</param>
    /// <param name="configAlarm">A list of configuration settings for the observations, including expiration times.</param>
    /// <returns>A list of patient observation alarms matching the filter criteria and expiration settings.</returns>
    public async Task<List<PatientObservationAlarm>> AggregatedPatientNotExpiredObservationsByField(
        ObjectId patientId,
        List<Field>? filterObservations,
        List<ConfigObservation> configAlarm)
    {
        try
        {
            var results = new List<PatientObservationAlarm>();
            IAsyncCursor<PatientObservationAlarm>? cursor;
            var builder = Builders<PatientObservationAlarm>.Filter;

            if (filterObservations != null)
            {
                foreach (var obs in filterObservations)
                {
                    FilterDefinition<PatientObservationAlarm> filter;
                    var conf = configAlarm.FirstOrDefault(c => c.Name == obs.Name);

                    if (obs is { OnlyExpired: true, Name: not null })
                        filter = builder.And(
                            builder.Eq(o => o.PatientId, patientId),
                            builder.Eq(o => o.Name, obs.Name),
                            builder.Eq("Expired", obs.OnlyExpired)
                            //builder.Eq(o => o.Expired, obs.OnlyExpired)
                        );
                    else
                        filter = builder.And(
                            builder.Eq(o => o.PatientId, patientId),
                            builder.Eq(o => o.Name, obs.Name)
                        );

                    if (conf is { Expires: not null })
                    {
                        var dateNow = DateTime.UtcNow.AddSeconds(conf.Expires.Value * -1);
                        filter = builder.And(
                            filter,
                            builder.Gte(o => o.Time, dateNow)
                        );
                    }

                    cursor = await Collection.FindAsync(
                        filter,
                        new FindOptions<PatientObservationAlarm>
                            { Sort = Builders<PatientObservationAlarm>.Sort.Descending("time").Descending("id") });

                    results.AddRange(cursor.ToEnumerable());
                }
            }
            else
            {
                var filter = builder.Eq(o => o.PatientId, patientId);

                cursor = await Collection.FindAsync(
                    filter,
                    new FindOptions<PatientObservationAlarm>
                        { Sort = Builders<PatientObservationAlarm>.Sort.Descending("time").Descending("id") });

                results.AddRange(cursor.ToEnumerable());
            }

            return results;
        }
        catch (Exception ex)
        {
            _logger.LogError("Error aggregated patient last observations by field {exMessage}", ex.Message);
            throw;
        }
    }

    /// <summary>
    /// Gets the name of the MongoDB collection for patient observation alarms. 
    /// The collection name is determined based on the API settings provided during the repository initialization. 
    /// If the collection name is not specified in the API settings, it defaults to "patients_alarms".
    /// </summary>
    /// <returns>The name of the MongoDB collection for patient observation alarms.</returns>
    public override string GetCollectionName()
    {
        return _apiSettings.PatientsAlarms ?? "patients_alarms";
    }

    /// <summary>
    /// Creates indexes for the patient observation alarms collection in MongoDB to optimize query performance.
    /// </summary>
    /// <returns>A task that represents the asynchronous operation of creating indexes.</returns>
    public override async Task CreateIndexes()
    {
        try
        {
            var options = new CreateIndexOptions { Background = true, Unique = false };
            var indexes = new List<CreateIndexModel<PatientObservationAlarm>>
            {
                new("{ patientid: 1 }", options),
                new("{ patientid: 1, name: 1 }", options),
                new("{ name: 1 }", options),
                new("{ patientid: 1, name: 1 , time: 1}", options)
            };
            await MongoUtils.EnsureIndexes(Collection, indexes);
        }
        catch (Exception e)
        {
            _logger.LogError(
                "error creating indexes for observation collection {eMessage} TRACE: {eStackTrace}", e.Message,
                e.StackTrace);
            throw;
        }
    }
}