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.Driver;
using Serilog;

namespace adas_core.Infrastructure.Repositories;

public class ObservationArchiveRepository : MongoRepository<PatientObservation>, IObservationArchiveRepository
{
    private readonly ApiSettings _apiSettings;

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

    


    public async Task<List<PatientObservation>> AggregatedPatientLastObservations(ObjectId patientId, int num,
        DateTime lastDate, List<string>? filterObservations = null)
    {
        filterObservations = await AggregatePatientObservations(patientId, filterObservations);
        var results = new List<PatientObservation>();

        foreach (var obs in filterObservations)
        {
            var filter = Builders<PatientObservation>.Filter.And(
                Builders<PatientObservation>.Filter.Eq(o => o.PatientId, patientId),
                Builders<PatientObservation>.Filter.Eq(o => o.Name, obs),
                Builders<PatientObservation>.Filter.Lte(o => o.Time, lastDate)
            );

            var sort = Builders<PatientObservation>.Sort.Descending(o => o.Time);

            results.AddRange(Collection.Find(filter).Sort(sort).Limit(num).ToEnumerable());
        }

        return results;
    }

    public override string GetCollectionName()
    {
        return _apiSettings.ArchivePatientsObservations ?? "archive_patients_observations";
    }

    public new async Task InsertOneAsync(PatientObservation patientObservation)
    {
        const int maxRetries = 2; // Número máximo de reintentos
        var retryCount = 0;

        while (true)
            try
            {
                await Collection.InsertOneAsync(patientObservation);
                return;
            }
            catch (MongoWriteException ex) when (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
            {
                retryCount++;

                Log.Warning(
                    "Duplicate key error encountered. Retrying with new ObjectId. Attempt {attempt} of {maxRetries}",
                    retryCount, maxRetries);

                patientObservation.Id = new ObjectId();
                Log.Information("Generated ObjectId: {objectId}", patientObservation.Id);

                if (retryCount >= maxRetries)
                {
                    Log.Error("Maximum retry attempts reached. Could not insert document due to duplicate key error.");
                    throw; // Re-lanzar la excepción después de alcanzar el número máximo de reintentos
                }
            }
            catch (Exception ex)
            {
                Log.Error("Error inserting patient observation: {exMessage}", ex.Message);
                throw;
            }
    }

    public async Task DeleteBeforeDate(DateTime date)
    {
        var filter = Builders<PatientObservation>.Filter.Lt(po => po.Time, date);
        await Collection.DeleteManyAsync(filter);
    }

    public async Task<long> InsertBatch(IEnumerable<PatientObservation> observations)
    {
        var writes = new List<WriteModel<PatientObservation>>();
        writes.AddRange(observations.Select(d => new InsertOneModel<PatientObservation>(d)));

        var bulkInsert = await Collection.BulkWriteAsync(writes);

        return bulkInsert.InsertedCount;
    }

    public async Task<List<PatientObservation>> FindAllFromPatient(ObjectId patientId)
    {
        var filter = Builders<PatientObservation>.Filter.Eq(p => p.PatientId, patientId);
        var result = await Collection.FindAsync(filter);

        return await result.ToListAsync();
    }

    private async Task<List<string>> AggregatePatientObservations(ObjectId patientId,
        List<string>? filterObservations = null)
    {
        var matchPatient = new BsonDocument
        {
            { "patientid", patientId },
            { "name", new BsonDocument { { "$ne", BsonNull.Value } } }
        };

        if (filterObservations == null || !filterObservations.Any())
        {
            // GET DISTINCT OBSERVATIONS
            var distinctObs = new BsonDocument
            {
                {
                    "$group", new BsonDocument
                    {
                        { "_id", "1" },
                        {
                            "obs", new BsonDocument
                            {
                                { "$addToSet", "$name" }
                            }
                        }
                    }
                }
            };
            var distinctPipeline = new[]
            {
                new()
                {
                    {
                        "$match", matchPatient
                    }
                },
                distinctObs
            };
            Debug.WriteLine("AggregatedArchivedPatientLastObservations distinct obs: \n" + distinctPipeline.ToJson());
            var resultList =
                await Collection.AggregateAsync<BsonDocument>(distinctPipeline,
                    new AggregateOptions { AllowDiskUse = true });
            var result = resultList.ToList().FirstOrDefault();

            if (result != null && result.Any())
                filterObservations = result.GetValue("obs").AsBsonArray.Select(it => it.AsString).ToList();
        }

        return filterObservations ?? [];
    }
}