using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
using adas_core.Infrastructure.Repositories;
using adas_core.Test.Utilities;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using Options = Microsoft.Extensions.Options.Options;

namespace adas_core.Test.Repositories;

[TestFixture]
[Category("Integration")]
public class DischargeRepositoryTest
{
    /// <summary>
    /// Performs one-time test setup for discharge integration tests by initializing API settings, dropping and recreating the "discharge" collection in the integration database, and instantiating the <see cref="DischargeRepository"/>.
    /// </summary>
    [OneTimeSetUp]
        public async Task Init()
        {
            // Initialize options and repository
            _optionsApiSettings = Options.Create(_apiSettings);
    
            await IntegrationDb.Database.DropCollectionAsync("discharge");
            await IntegrationDb.Database.CreateCollectionAsync("discharge");
    
            _dischargeRepository = new DischargeRepository(_optionsApiSettings, IntegrationDb.Database);
        }

    private DischargeRepository _dischargeRepository;
    private readonly ApiSettings _apiSettings = new();
    private IOptions<ApiSettings> _optionsApiSettings;

    /// <summary>
    /// Verifies that <c>InsertOneAsync</c> successfully persists a discharge document and that the
    /// inserted record can be retrieved by its identifier.
    /// </summary>
    [Test]
        public async Task InsertOneAsync_ShouldInsertDischarge()
        {
            // Arrange
            var discharge = TestUtilities.CreateValidDischarge();
    
            // Act
            await _dischargeRepository.InsertOneAsync(discharge);
    
            var result = await _dischargeRepository.FindById(discharge.Id);
            Assert.That(result, Is.Not.Null);
        }

    /// <summary>
    /// Verifies that the <c>Delete</c> repository operation removes a discharge so that subsequent lookups by its identifier return no result.
    /// </summary>
    [Test]
        public async Task Delete_ShouldDeleteDischarge()
        {
            // Arrange
            var discharge = TestUtilities.CreateValidDischarge();
            await _dischargeRepository.InsertOneAsync(discharge);
    
            // Act
            await _dischargeRepository.Delete(discharge.Id);
    
            var result = await _dischargeRepository.FindById(discharge.Id);
            Assert.That(result, Is.Null);
        }

    /// <summary>
    /// Verifies that an existing discharge record is successfully updated in the repository with new field values.
    /// </summary>
    [Test]
        public async Task Update_DischargeSuccessfullyUpdated()
        {
            // Arrange
            var pocId = new ObjectId();
            var discharge = TestUtilities.CreateValidDischarge();
            discharge.PointOfCareId = pocId;
            await _dischargeRepository.InsertOneAsync(discharge);
    
            // Act
            discharge.Service = "UpdatedService";
            await _dischargeRepository.Update(discharge);
    
            // Retrieve the discharge from the database
            var updatedDischarge = await _dischargeRepository.GetDischargeByPointOfCareId(pocId);
    
            // Assert
            Assert.That(updatedDischarge, Is.Not.Null);
            Assert.That(updatedDischarge?.Service, Is.EqualTo("UpdatedService"));
        }

    // [Test]
    // public async Task UpdateUnit_UnitNameSuccessfullyUpdated()
    // {
    //     // Arrange
    //     var discharge = TestUtilities.CreateValidDischarge();
    //     await _dischargeRepository.InsertOneAsync(discharge);
    //
    //     // Act
    //     var newUnitName = "NewUnitName";
    //     await _dischargeRepository.UpdateUnit(discharge.Id, newUnitName);
    //
    //     // Retrieve the discharge from the database
    //     var updatedDischarge = await _dischargeRepository.FindById(discharge.Id);
    //
    //     // Assert
    //     Assert.That(updatedDischarge, Is.Not.Null);
    //     Assert.That(updatedDischarge?.PatientLocation, Is.Not.Null);
    //     Assert.That(updatedDischarge?.PatientLocation?.UnitName, Is.EqualTo(newUnitName));
    // }

    // [Test]
    // public async Task UpdatePatient_PatientSuccessfullyUpdated()
    // {
    //     // Arrange
    //     var discharge = TestUtilities.CreateValidDischarge();
    //     await _dischargeRepository.InsertOneAsync(discharge);
    //
    //     // Create a new patient object with updated information
    //     var updatedPatient = TestUtilities.CreateValidPatient();
    //
    //     // Act
    //     await _dischargeRepository.UpdatePatient(discharge.Id, updatedPatient);
    //
    //     // Retrieve the discharge from the database
    //     var updatedDischarge = await _dischargeRepository.FindById(discharge.Id);
    //
    //     // Assert
    //     Assert.That(updatedDischarge, Is.Not.Null);
    //     Assert.That(updatedDischarge?.Patient, Is.Not.Null);
    //     Assert.That(updatedDischarge?.Patient?.Id, Is.EqualTo(updatedPatient.Id));
    // }

    /// <summary>
    /// Verifies that the repository's <c>FindAll</c> method returns all discharge records previously inserted via <c>InsertManyAsync</c>, returning a non-null, non-empty collection whose count matches the number of inserted records.
    /// </summary>
    [Test]
        public async Task FindAll_ReturnsAllDischarges()
        {
            // Arrange: Prepare test data
            var expectedDischarges = new List<Discharge>
            {
                new() { Id = ObjectId.GenerateNewId(), DischargeDate = DateTime.UtcNow },
                new() { Id = ObjectId.GenerateNewId(), DischargeDate = DateTime.UtcNow },
                new() { Id = ObjectId.GenerateNewId(), DischargeDate = DateTime.UtcNow }
            };
    
            await _dischargeRepository.InsertManyAsync(expectedDischarges);
    
            // Act: Call the method under test
            var actualDischarges = await _dischargeRepository.FindAll();
    
            var discharges = actualDischarges.ToList();
            Assert.That(discharges, Is.Not.Null);
            Assert.That(discharges, Is.Not.Empty);
            Assert.That(discharges.Count(), Is.EqualTo(expectedDischarges.Count()));
        }

    /// <summary>
    /// Verifies that the repository's <c>FindById</c> method returns the matching <c>Discharge</c> document when queried with an existing identifier.
    /// </summary>
    [Test]
        public async Task FindById_ExistingId_ReturnsDischarge()
        {
            var id = ObjectId.GenerateNewId();
            var expectedDischarge = new Discharge { Id = id, DischargeDate = DateTime.UtcNow };
            await _dischargeRepository.InsertOneAsync(expectedDischarge);
    
            var actualDischarge = await _dischargeRepository.FindById(id);
    
            Assert.That(actualDischarge, Is.Not.Null);
            Assert.That(actualDischarge?.Id, Is.EqualTo(expectedDischarge.Id));
        }

    /// <summary>
    /// Verifies that the discharge repository's FindById method returns <c>null</c> when queried with a non-existent <see cref="ObjectId"/>.
    /// </summary>
    [Test]
        public async Task FindById_NonExistentId_ReturnsNull()
        {
            var id = ObjectId.GenerateNewId();
    
            var actualDischarge = await _dischargeRepository.FindById(id);
    
            Assert.That(actualDischarge, Is.Null);
        }

    // [Test]
    // public async Task FindByUnit_ExistingUnit_ReturnsDischarges()
    // {
    //     // Arrange: Prepare test data
    //     var unitName = "TestUnit";
    //     var expectedDischarges =
    //             new Discharge
    //                 { Id = ObjectId.GenerateNewId(), PatientLocation = new PatientLocation { UnitName = unitName } };
    //     await _dischargeRepository.InsertOneAsync(expectedDischarges);
    //
    //     // Act: Call the method under test
    //     var actualDischarges = await _dischargeRepository.FindByUnit(unitName);
    //
    //     // Assert: Verify the result
    //     Assert.That(actualDischarges, Is.Not.Null);
    //     Assert.That(actualDischarges?.ToList().First().PatientLocation?.UnitName, Is.EquivalentTo(expectedDischarges.PatientLocation.UnitName));
    // }

    /// <summary>
    /// Verifies that the discharge repository's FindByDestination method returns the matching discharge records when queried with an existing destination.
    /// </summary>
    [Test]
        public async Task FindByDestination_ExistingDestination_ReturnsDischarges()
        {
            // Arrange: Prepare test data
            var destination = "TestDestination";
            var expectedDischarges =
                new Discharge
                    { Id = ObjectId.GenerateNewId(), Destination = destination };
            await _dischargeRepository.InsertOneAsync(expectedDischarges);
    
            // Act: Call the method under test
            var actualDischarges = await _dischargeRepository.FindByDestination(destination);
    
            // Assert: Verify the result
            var discharges = actualDischarges?.ToList() ?? [];
            Assert.That(discharges, Is.Not.Null);
            Assert.That(discharges.First().Id, Is.EqualTo(expectedDischarges.Id));
        }

    /// <summary>
    /// Verifies that <c>FindByDestination</c> returns an empty collection when the provided destination does not match any stored discharge records.
    /// </summary>
    [Test]
        public async Task FindByDestination_NonExistentDestination_ReturnsEmptyList()
        {
            // Arrange: Use a non-existent destination
            var destination = "NonExistentDestination";
    
            // Act: Call the method under test
            var actualDischarges = await _dischargeRepository.FindByDestination(destination);
    
            // Assert: Verify that the result is an empty list
            Assert.That(actualDischarges, Is.Empty);
        }

    /// <summary>
    /// Verifies that <see cref="DischargeRepository.FindByPoCId"/> returns the matching discharge
    /// when a discharge is associated with the supplied point-of-care identifier.
    /// </summary>
    [Test]
        public async Task FindByPoCId_ExistingPoCId_ReturnsDischarges()
        {
            // Arrange: Prepare test data
            var pocId = ObjectId.GenerateNewId();
            var expectedDischarges =
                new Discharge
                    { Id = ObjectId.GenerateNewId(), PointOfCareId = pocId };
            await _dischargeRepository.InsertOneAsync(expectedDischarges);
    
            // Act: Call the method under test
            var actualDischarges = await _dischargeRepository.FindByPoCId(pocId);
    
            // Assert: Verify the result
            var discharges = actualDischarges?.ToList() ?? [];
            Assert.That(discharges, Is.Not.Null);
            Assert.That(discharges.First().Id, Is.EqualTo(expectedDischarges.Id));
        }

    /// <summary>
    /// Verifies that the discharge repository's FindByPoCId method returns an empty list when queried with a non-existent PoCId.
    /// </summary>
    [Test]
        public async Task FindByPoCId_NonExistentPoCId_ReturnsEmptyList()
        {
            // Arrange: Use a non-existent PoCId
            var pocId = ObjectId.GenerateNewId();
    
            // Act: Call the method under test
            var actualDischarges = await _dischargeRepository.FindByPoCId(pocId);
    
            // Assert: Verify that the result is an empty list
            Assert.That(actualDischarges, Is.Empty);
        }

    /// <summary>
    /// Verifies that <see cref="DischargeRepository.FindByService"/> returns the matching discharge record when a previously inserted discharge exists for the specified service name.
    /// </summary>
    [Test]
        public async Task FindByService_ExistingService_ReturnsDischarges()
        {
            // Arrange: Prepare test data
            var service = "TestService";
            var expectedDischarges =
                new Discharge
                    { Id = ObjectId.GenerateNewId(), Service = service };
            await _dischargeRepository.InsertOneAsync(expectedDischarges);
    
            // Act: Call the method under test
            var actualDischarges = await _dischargeRepository.FindByService(service);
    
            // Assert: Verify the result
            var discharges = actualDischarges?.ToList() ?? [];
            Assert.That(discharges, Is.Not.Null);
            Assert.That(discharges.First().Id, Is.EqualTo(expectedDischarges.Id));
        }

    /// <summary>
    /// Verifies that <c>FindByService</c> returns an empty list when queried with a service name that does not exist in the repository.
    /// </summary>
    [Test]
        public async Task FindByService_NonExistentService_ReturnsEmptyList()
        {
            // Arrange: Use a non-existent service
            var service = "NonExistentService";
    
            // Act: Call the method under test
            var actualDischarges = await _dischargeRepository.FindByService(service);
    
            // Assert: Verify that the result is an empty list
            Assert.That(actualDischarges, Is.Empty);
        }

    // [Test]
    // public async Task GetDischargeByLocation_ExistingLocation_ReturnsDischarge()
    // {
    //     // Arrange: Prepare test data
    //     var location = new PatientLocation("TestUnit", "TestBed", "TestRoom");
    //     var expectedDischarges =
    //         new Discharge
    //             { Id = ObjectId.GenerateNewId(), PatientLocation = location};
    //     await _dischargeRepository.InsertOneAsync(expectedDischarges);
    //
    //     // Act: Call the method under test
    //     var actualDischarge = await _dischargeRepository.GetDischargeByLocation(location);
    //
    //     // Assert: Verify the result
    //     Assert.That(actualDischarge, Is.Not.Null);
    //     Assert.That(actualDischarge?.Id, Is.EqualTo(expectedDischarges.Id));
    // }

    /// <summary>
    /// Verifies that <c>GetDischargeByLocation</c> returns <c>null</c> when queried with a location
    /// that does not exist in the repository.
    /// </summary>
    [Test]
        public async Task GetDischargeByLocation_NonExistentLocation_ReturnsNull()
        {
            // Arrange: Use a non-existent location
            var location = new PatientLocation("NonExistentUnit", "NonExistentBed", "NonExistentRoom");
    
            // Act: Call the method under test
            var actualDischarge = await _dischargeRepository.GetDischargeByLocation(location);
    
            // Assert: Verify that the result is null
            Assert.That(actualDischarge, Is.Null);
        }

    /// <summary>
    /// Verifies that <c>GetDischargeByPointOfCareId</c> returns the matching <c>Discharge</c> when queried with a point of care ID that already exists in the repository.
    /// </summary>
    [Test]
        public async Task GetDischargeByPointOfCareId_ExistingId_ReturnsDischarge()
        {
            // Arrange: Prepare test data
            var expectedId = ObjectId.GenerateNewId();
            var expectedDischarge =
                new Discharge
                    { Id = ObjectId.GenerateNewId(), PointOfCareId = expectedId };
            await _dischargeRepository.InsertOneAsync(expectedDischarge);
    
            // Act: Call the method under test
            var actualDischarge = await _dischargeRepository.GetDischargeByPointOfCareId(expectedId);
    
            // Assert: Verify the result
            Assert.That(actualDischarge, Is.Not.Null);
            Assert.That(actualDischarge?.Id, Is.EqualTo(expectedDischarge.Id));
        }

    /// <summary>
    /// Verifies that <c>GetDischargeByPointOfCareId</c> returns <c>null</c> when called with a non-existent point of care ID.
    /// </summary>
    [Test]
        public async Task GetDischargeByPointOfCareId_NonExistentId_ReturnsNull()
        {
            // Arrange: Use a non-existent ID
            var nonExistentId = ObjectId.GenerateNewId();
    
            // Act: Call the method under test
            var actualDischarge = await _dischargeRepository.GetDischargeByPointOfCareId(nonExistentId);
    
            // Assert: Verify that the result is null
            Assert.That(actualDischarge, Is.Null);
        }

    /// <summary>
    /// Verifies that <c>GetByPatientId</c> returns the matching <see cref="Discharge"/> record
    /// when a discharge exists for the specified patient identifier.
    /// </summary>
    [Test]
        public async Task GetByPatientId_ExistingPatientId_ReturnsDischarge()
        {
            // Arrange: Prepare test data
            var expectedId = ObjectId.GenerateNewId();
            var expectedDischarge =
                new Discharge
                    { Id = ObjectId.GenerateNewId(), PatientId = expectedId };
            await _dischargeRepository.InsertOneAsync(expectedDischarge);
    
            // Act: Call the method under test
            var actualDischarge = await _dischargeRepository.GetByPatientId(expectedId);
    
            // Assert: Verify the result
            Assert.That(actualDischarge, Is.Not.Null);
            Assert.That(actualDischarge?.Id, Is.EqualTo(expectedDischarge.Id));
        }

    /// <summary>
    /// Verifies that the <see cref="DischargeRepository.GetByPatientId"/> repository method returns <c>null</c>
    /// when called with a patient ID that does not exist in the data store.
    /// </summary>
    [Test]
        public async Task GetByPatientId_NonExistentPatientId_ReturnsNull()
        {
            // Arrange: Use a non-existent patient ID
            var nonExistentId = ObjectId.GenerateNewId();
    
            // Act: Call the method under test
            var actualDischarge = await _dischargeRepository.GetByPatientId(nonExistentId);
    
            // Assert: Verify that the result is null
            Assert.That(actualDischarge, Is.Null);
        }
}