using System.Diagnostics;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;

namespace adas_core.Infrastructure.Repositories;

/// <summary>
/// Provides a MongoDB-backed repository implementation for <see cref="PatientRecordingAlert"/> entities, exposing data access operations defined by the <see cref="IRecordingAlertRepository"/> contract.
/// </summary>
/// <typeparam name="PatientRecordingAlert">The type of recording alert entity managed by the repository.</typeparam>
public class RecordingAlertRepository : MongoRepository<PatientRecordingAlert>, IRecordingAlertRepository
{
    private readonly ApiSettings _apiSettings;

    public RecordingAlertRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
    {
        if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
        _apiSettings = apiSettings.Value;
    } //For testing

   

    /// <summary>
    /// Retrieves the collection name for patients recording alerts, returning the configured API setting value or a default fallback when the setting is not available.
    /// </summary>
    /// <returns>The collection name from <c>_apiSettings.PatientsRecordingAlerts</c>, or the default value "patients_recordingalerts" if the setting is null.</returns>
    public override string GetCollectionName()
        {
            return _apiSettings.PatientsRecordingAlerts ?? "patients_recordingalerts";
        }

    /// <summary>
    /// Retrieves the most recent observations for a specified patient from MongoDB using an aggregation pipeline,
    /// grouped by observation name, returning up to the specified number of records per group.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose observations are being retrieved.</param>
    /// <param name="num">The maximum number of recent observations to return per grouped observation name.</param>
    /// <returns>A task containing a list of <see cref="PatientRecordingAlert"/> objects representing the aggregated last observations for the patient.</returns>
    public async Task<List<PatientRecordingAlert>> AggregatedPatientLastObservations(ObjectId patientId, int num)
        {
            var match = new BsonDocument
            {
                { "patientid", patientId }
            };
            var pipeline = new BsonDocument[]
            {
                new()
                {
                    {
                        "$match", match
                    }
                },
                new()
                {
                    {
                        "$sort", new BsonDocument
                        {
                            { "codingSystem", 1 },
                            { "code", 1 },
                            { "time", -1 }
                        }
                    }
                },
                new()
                {
                    {
                        "$group", new BsonDocument
                        {
                            {
                                "_id", new BsonDocument
                                {
                                    // { "codingSystem", "$codingSystem" } ,
                                    // { "code", "$code" } ,
                                    { "name", "$name" }
                                }
                            },
                            {
                                "results", new BsonDocument
                                {
                                    { "$push", "$$ROOT" }
                                }
                            }
                        }
                    }
                },
                new()
                {
                    {
                        "$project", new BsonDocument
                        {
                            {
                                "results", new BsonDocument
                                {
                                    { "$slice", new BsonArray { "$results", num } }
                                }
                            }
                        }
                    }
                }
            };
            Debug.WriteLine(pipeline.ToJson());
            var result =
                await Collection.AggregateAsync<BsonDocument>(pipeline, new AggregateOptions { AllowDiskUse = true });
            var obs = new List<PatientRecordingAlert>();
            result.ToList().ForEach(it =>
            {
                foreach (var obit in it.GetValue("results").AsBsonArray)
                {
                    var obsit = obit.AsBsonDocument;
                    var pobs = BsonSerializer.Deserialize<PatientRecordingAlert>(obsit);
                    pobs.PatientId = patientId;
                    obs.Add(pobs);
                }
            });
            return obs;
        }

    /// <summary>
    /// Deletes a patient recording alert identified by the specified identifier from the underlying collection.
    /// </summary>
    /// <param name="id">The unique identifier of the patient recording alert to delete.</param>
    public new async Task DeleteAsync(ObjectId id)
        {
            var filter = Builders<PatientRecordingAlert>.Filter.Eq(obs => obs.Id, id);
            await Collection.DeleteOneAsync(filter);
        }


    /// <summary>
    /// Inserts a new patient recording alert into the underlying collection asynchronously.
    /// </summary>
    /// <param name="patientRecordingAlert">The patient recording alert document to be inserted.</param>
    public override async Task InsertOneAsync(PatientRecordingAlert patientRecordingAlert)
        {
            await Collection.InsertOneAsync(patientRecordingAlert);
        }

    /// <summary>
    /// Deletes a single PatientRecordingAlert document whose Name matches the provided value and whose Time is older than the retention period defined by <paramref name="retentionPolicyValue"/> days from now.
    /// </summary>
    /// <param name="name">The name of the patient recording alert used to match the document for deletion.</param>
    /// <param name="retentionPolicyValue">The retention period in days; the cutoff time is calculated as UTC now minus this number of days.</param>
    public async Task DeleteOlderDaysAsync(string name, int retentionPolicyValue)
        {
            var filterBuilder = Builders<PatientRecordingAlert>.Filter;
            var filter = filterBuilder.And(
                filterBuilder.Eq(obs => obs.Name, name),
                filterBuilder.Lt(obs => obs.Time, DateTime.UtcNow.AddDays(-1 * retentionPolicyValue))
            );
    
            await Collection.DeleteOneAsync(filter);
        }


    /// <summary>
    /// Deletes all patient recording alerts associated with the specified patient identifier.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose recording alerts will be removed.</param>
    public async Task DeleteByPatientId(ObjectId patientId)
        {
            var filter = Builders<PatientRecordingAlert>.Filter.Eq(po => po.PatientId, patientId);
            await Collection.DeleteManyAsync(filter);
        }


    /// <summary>
    /// Deletes <see cref="PatientRecordingAlert"/> records that exceed the retention policy, keeping only the most recent records for the specified name. The method filters alerts by name, sorts them by time in descending order, skips the most recent records up to the retention threshold, and removes the remaining older entries.
    /// </summary>
    /// <param name="name">The name used to filter the alerts subject to the retention policy.</param>
    /// <param name="retentionPolicyValue">The number of most recent records to retain; any additional older records will be deleted.</param>
    public async Task DeleteOlderNumberAsync(string name, int retentionPolicyValue)
        {
            var filterBuilder = Builders<PatientRecordingAlert>.Filter;
            var sortBuilder = Builders<PatientRecordingAlert>.Sort;
    
            var filter = filterBuilder.Eq(obs => obs.Name, name);
            var projection = Builders<PatientRecordingAlert>.Projection.Include(obs => obs.Id).Include(obs => obs.Time);
            var sort = sortBuilder.Descending("time");
            var options = new FindOptions<PatientRecordingAlert>
            {
                Projection = projection,
                Sort = sort,
                Skip = retentionPolicyValue
            };
    
            var result = await Collection.FindAsync(filter, options);
    
            await result.ForEachAsync(async obs =>
            {
                var idFilter = filterBuilder.Eq(ob => ob.Id, obs.Id);
                await Collection.DeleteOneAsync(idFilter);
            });
        }


    /// <summary>
    /// Retrieves all patient recording alerts associated with the specified patient identifier.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose recording alerts should be retrieved.</param>
    /// <returns>A task representing the asynchronous operation, containing a cursor over the matching patient recording alerts.</returns>
    public async Task<IAsyncCursor<PatientRecordingAlert>> FindByPatientIdAsync(ObjectId patientId)
        {
            var filter = Builders<PatientRecordingAlert>.Filter.Eq(ob => ob.PatientId, patientId);
            return await Collection.FindAsync(filter);
        }


    /// <summary>
    /// Retrieves the most recent observations for a specified patient, filtered by observation name and ordered by time in descending order.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient whose observations are being queried.</param>
    /// <param name="name">The name of the observation used to filter the results.</param>
    /// <param name="num">The maximum number of observations to return. Defaults to 2.</param>
    /// <returns>A list of <see cref="PatientRecordingAlert"/> entries containing the latest matching observations for the patient.</returns>
    public async Task<List<PatientRecordingAlert>> FindLastObservations(ObjectId patientId, string name, int num = 2)
        {
            var filterBuilder = Builders<PatientRecordingAlert>.Filter;
            var sortBuilder = Builders<PatientRecordingAlert>.Sort;
    
            var filter = filterBuilder.And(
                filterBuilder.Eq(ob => ob.PatientId, patientId),
                filterBuilder.Eq(ob => ob.Name, name)
            );
    
            var sort = sortBuilder.Descending("time");
    
            var options = new FindOptions<PatientRecordingAlert>
            {
                Sort = sort,
                Limit = num
            };
    
            var result = await Collection.FindAsync(filter, options);
    
            return await result.ToListAsync();
        }


    /// <summary>
    /// Updates many records by replacing the old <see cref="ObjectId"/> with the new one, identified by the specified <paramref name="nameId"/>.
    /// Delegates the operation to the asynchronous <c>UpdateManyObjectIdAsync</c> implementation.
    /// </summary>
    /// <param name="nameId">The identifier of the field or collection used to target the records to update.</param>
    /// <param name="id">The new <see cref="ObjectId"/> value to assign to the matched records.</param>
    /// <param name="oldId">The existing <see cref="ObjectId"/> value to be replaced.</param>
    public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
        {
            await UpdateManyObjectIdAsync(nameId, id, oldId);
        }

    /// <summary>
    /// Creates MongoDB indexes for the PatientRecordingAlert collection, including a non-unique background index on the patientid field to optimize queries by patient.
    /// </summary>
    public override async Task CreateIndexes()
        {
            var options = new CreateIndexOptions { Background = true, Unique = false };
            var indexes = new List<CreateIndexModel<PatientRecordingAlert>>
            {
                new("{ patientid: 1 }", options)
            };
    
            await MongoUtils.EnsureIndexes(Collection, indexes);
        }
}