using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Infrastructure.Repositories;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using Options = Microsoft.Extensions.Options.Options;

namespace adas_core.Test.Repositories;

[TestFixture]
[Category("Integration")]
public class RecordingAlertRepositoryTest
{
    /// <summary>
    /// One-time test setup that initializes the recording alert repository with two sample patient recording alerts
    /// and ensures the underlying collection contains exactly two documents for integration test scenarios.
    /// </summary>
    [OneTimeSetUp]
        public async Task Init()
        {
            _optionsApiSettings = Options.Create(_apiSettings);
    
            var recordingAlert1 = new PatientRecordingAlert
            {
                PatientId = PatientId,
                IsRecording = true,
                Time = Now
            };
    
            var recordingAlert2 = new PatientRecordingAlert
            {
                PatientId = ObjectId.GenerateNewId(),
                IsRecording = true,
                Time = Now.AddDays(-10)
            };
    
            await IntegrationDb.Database.DropCollectionAsync("archive_patients_recordingalerts");
            await IntegrationDb.Database.CreateCollectionAsync("archive_patients_recordingalerts");
    
            _repository = new RecordingAlertRepository(_optionsApiSettings, IntegrationDb.Database);
    
            await _repository.InsertOneAsync(recordingAlert1);
            await _repository.InsertOneAsync(recordingAlert2);
    
            Assert.That(await _repository.Collection.CountDocumentsAsync(_ => true), Is.EqualTo(2));
        }

    private RecordingAlertRepository _repository;

    private readonly ApiSettings _apiSettings = new()
    {
        ArchivePatientsRecordingalerts = "archive_patients_recordingalerts"
    };

    private IOptions<ApiSettings> _optionsApiSettings;

    private static readonly DateTime Now = DateTime.Now;
    private static readonly ObjectId PatientId = ObjectId.GenerateNewId();


    /// <summary>
    /// Verifies that the repository's DeleteAsync method successfully removes a PatientRecordingAlert document, confirming the record exists prior to deletion and is no longer retrievable afterwards.
    /// </summary>
    [Test]
        public async Task DeleteAsync()
        {
            var recordingAlert = new PatientRecordingAlert
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = ObjectId.GenerateNewId(),
                IsRecording = true,
                Time = Now
            };
    
            await _repository.InsertOneAsync(recordingAlert);
    
            var result = await _repository.Collection.FindAsync(p => p.Id == recordingAlert.Id);
    
            Assert.That(result.FirstOrDefault(), Is.Not.Null);
    
            await _repository.DeleteAsync(recordingAlert.Id);
    
            var resultDelete = await _repository.Collection.FindAsync(p => p.Id == recordingAlert.Id);
    
            Assert.That(resultDelete.FirstOrDefault(), Is.Null);
        }


    /// <summary>
    /// Tests that the <c>DeleteOlderDaysAsync</c> repository method removes patient recording alerts that exceed the specified age threshold and match the given recording name, while preserving alerts within the threshold.
    /// </summary>
    [Test]
        public async Task DeleteOlderDaysAsync()
        {
            var recordingAlert = new PatientRecordingAlert
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = ObjectId.GenerateNewId(),
                IsRecording = true,
                Time = Now,
                Name = "recordingName1"
            };
    
            var recordingAlertOld = new PatientRecordingAlert
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = recordingAlert.PatientId,
                IsRecording = true,
                Time = Now.AddDays(-5),
                Name = "recordingName1"
            };
    
            await _repository.InsertOneAsync(recordingAlert);
            await _repository.InsertOneAsync(recordingAlertOld);
    
            var result = await _repository.Collection.FindAsync(p => p.PatientId == recordingAlert.PatientId);
    
            Assert.That(result.ToList(), Has.Count.EqualTo(2));
    
            await _repository.DeleteOlderDaysAsync("recordingName1", 2);
    
            var resultDelete = await _repository.Collection.FindAsync(p => p.PatientId == recordingAlert.PatientId);
    
            Assert.That(resultDelete.ToList(), Has.Count.EqualTo(1));
    
            await _repository.Collection.DeleteManyAsync(d => d.PatientId == recordingAlert.PatientId);
    
            var resultClear = await _repository.Collection.FindAsync(p => p.PatientId == recordingAlert.PatientId);
    
            Assert.That(resultClear.ToList(), Has.Count.EqualTo(0));
        }

    /// <summary>
    /// Verifies that <c>DeleteOlderNumberAsync</c> removes older records with the specified name, keeping only the most recent ones according to the provided count.
    /// Inserts two <see cref="PatientRecordingAlert"/> entries sharing the same name but different timestamps, asserts both exist, performs the deletion, and confirms that only one record remains.
    /// </summary>
    [Test]
        public async Task DeleteOlderNumberAsync()
        {
            var recordingAlert = new PatientRecordingAlert
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = ObjectId.GenerateNewId(),
                IsRecording = true,
                Time = Now,
                Name = "recordingName1"
            };
    
            var recordingAlertOld = new PatientRecordingAlert
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = recordingAlert.PatientId,
                IsRecording = true,
                Time = Now.AddDays(-5),
                Name = "recordingName1"
            };
    
            await _repository.InsertOneAsync(recordingAlert);
            await _repository.InsertOneAsync(recordingAlertOld);
    
            var result = await _repository.Collection.FindAsync(p => p.Name == "recordingName1");
    
            Assert.That(result.ToList(), Has.Count.EqualTo(2));
    
            await _repository.DeleteOlderNumberAsync("recordingName1", 1);
    
            var resultDelete = await _repository.Collection.FindAsync(p => p.Name == "recordingName1");
    
            Assert.That(resultDelete.ToList(), Has.Count.EqualTo(1));
        }

    /// <summary>
    /// Verifies that <c>DeleteByPatientId</c> removes a patient's recording alert from the repository, ensuring the alert cannot be retrieved after deletion.
    /// </summary>
    [Test]
        public async Task DeleteByPatientId()
        {
            var recordingAlert = new PatientRecordingAlert
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = ObjectId.GenerateNewId(),
                IsRecording = true,
                Time = Now
            };
    
            var recordingAlertOld = new PatientRecordingAlert
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = recordingAlert.PatientId,
                IsRecording = true,
                Time = Now.AddDays(-5)
            };
    
            await _repository.InsertOneAsync(recordingAlert);
            await _repository.InsertOneAsync(recordingAlertOld);
    
            var result = await _repository.Collection.FindAsync(p => p.PatientId == recordingAlert.PatientId);
    
            Assert.That(result.FirstOrDefault(), Is.Not.Null);
    
            await _repository.DeleteByPatientId(recordingAlert.PatientId);
    
            var resultDelete = await _repository.Collection.FindAsync(p => p.PatientId == recordingAlert.PatientId);
    
            Assert.That(resultDelete.FirstOrDefault(), Is.Null);
        }

    /// <summary>
    /// Verifies that the repository returns a non-null result when searching for records by patient identifier.
    /// </summary>
    [Test]
        public async Task FindByPatientIdAsync()
        {
            var result = await _repository.FindByPatientIdAsync(PatientId);
    
            Assert.That(result.FirstOrDefault(), Is.Not.Null);
        }

    /// <summary>
    /// Verifies that <c>FindLastObservations</c> returns only the most recent recording alert
    /// for a given patient and recording name, filtering out older entries based on the
    /// specified count limit, and that the returned observation matches the expected time.
    /// </summary>
    [Test]
        public async Task FindLastObservations()
        {
            var recordingAlert = new PatientRecordingAlert
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = ObjectId.GenerateNewId(),
                IsRecording = true,
                Time = Now,
                Name = "recordingName1"
            };
    
            var recordingAlertOld = new PatientRecordingAlert
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = recordingAlert.PatientId,
                IsRecording = true,
                Time = Now.AddDays(-5),
                Name = "recordingName1"
            };
    
            await _repository.InsertOneAsync(recordingAlert);
            await _repository.InsertOneAsync(recordingAlertOld);
    
            var result = await _repository.Collection.FindAsync(_ => true);
    
            Assert.That(result, Is.Not.Null);
            Assert.That(result.ToList(), Has.Count.GreaterThan(2));
    
            var resultFind = await _repository.FindLastObservations(recordingAlert.PatientId, "recordingName1", 1);
    
            Assert.That(resultFind, Is.Not.Null);
            using (Assert.EnterMultipleScope())
            {
                Assert.That(resultFind.ToList(), Has.Count.EqualTo(1));
                Assert.That(resultFind.FirstOrDefault()?.Time.Day, Is.EqualTo(Now.Day));
                Assert.That(resultFind.FirstOrDefault()?.Time.Month, Is.EqualTo(Now.Month));
                Assert.That(resultFind.FirstOrDefault()?.Time.Year, Is.EqualTo(Now.Year));
            };
            await _repository.DeleteByPatientId(recordingAlert.PatientId);
    
            var resultDelete = await _repository.Collection.FindAsync(p => p.PatientId == recordingAlert.PatientId);
    
            Assert.That(resultDelete.FirstOrDefault(), Is.Null);
        }

    /// <summary>
    /// Tests the repository's ability to update the <c>patientid</c> ObjectId field across records, verifying that after the update the original patient identifier no longer returns any alerts while the new patient identifier does, and that deletion by patient ID subsequently removes the record.
    /// </summary>
    [Test]
        public async Task UpdateManyObjectId()
        {
            var recordingAlert = new PatientRecordingAlert
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = ObjectId.GenerateNewId(),
                IsRecording = true,
                Time = Now
            };
    
            var newPatientId = ObjectId.GenerateNewId();
    
            await _repository.InsertOneAsync(recordingAlert);
    
            var result = await _repository.FindByPatientIdAsync(recordingAlert.PatientId);
    
            Assert.That(result.FirstOrDefault(), Is.Not.Null);
    
            await _repository.UpdateManyObjectId("patientid", newPatientId, recordingAlert.PatientId);
    
            var resultOldId = await _repository.FindByPatientIdAsync(recordingAlert.PatientId);
    
            Assert.That(resultOldId.FirstOrDefault(), Is.Null);
    
            var resultNewId = await _repository.FindByPatientIdAsync(newPatientId);
    
            Assert.That(resultNewId.FirstOrDefault(), Is.Not.Null);
    
            await _repository.DeleteByPatientId(newPatientId);
    
            var resultDelete = await _repository.FindByPatientIdAsync(newPatientId);
    
            Assert.That(resultDelete.FirstOrDefault(), Is.Null);
        }
}