using System.Security.Claims;
using adas_core.Application.Exceptions;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models;
using adas_core.Domain.Models.MongoModels;
using adas_core.Test.Utilities;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using Moq;

namespace adas_core.Test.Services;

/// <summary>
/// Provides unit tests for the <see cref="DischargeService"/> class, verifying the behavior and correctness of discharge-related operations.
/// </summary>
public class DischargeServiceTest
{
    private readonly Mock<ILocalAuditService> _auditServiceMock = new();
    private readonly Mock<IClientMessageService> _clientMessageServiceMock = new();
    private readonly Mock<IDischargeRepository> _dischargeRepositoryMock = new();
    private readonly Mock<IHttpContextAccessor> _httpContextAccessorMock = new();
    private readonly Mock<ILogger<DischargeService>> _loggerMock = new();
    private readonly Mock<IMasterListServiceFactory> _masterMock = new();
    private readonly Mock<Lazy<IPatientService>> _patientServiceLazyMock = new();

    private readonly Mock<IPointOfCareService> _pointOfCareServiceMock = new();
    private readonly Mock<ISubscribersService> _subscribersServiceMock = new();
    private readonly Mock<IUnitService> _unitServiceMock = new();
    private DischargeService _dischargeService = null!;

    /// <summary>
    /// Sets up the test environment by creating a mock authenticated user context and instantiating the <see cref="DischargeService"/> with its required dependencies for use in unit tests.
    /// </summary>
    [SetUp]
        public void Setup()
        {
            var userClaims = new ClaimsPrincipal(new ClaimsIdentity([
                new Claim(ClaimTypes.Name, "TestUser")
            ], "mock"));
    
            var httpContextMock = new DefaultHttpContext
            {
                User = userClaims
            };
    
            _httpContextAccessorMock.Setup(accessor => accessor.HttpContext)
                .Returns(httpContextMock);
            _dischargeService = new DischargeService(
                _loggerMock.Object,
                _subscribersServiceMock.Object,
                _dischargeRepositoryMock.Object,
                _patientServiceLazyMock.Object,
                _clientMessageServiceMock.Object,
                _pointOfCareServiceMock.Object,
                _httpContextAccessorMock.Object,
                _auditServiceMock.Object,
                _unitServiceMock.Object,
                _masterMock.Object);
        }

    /// <summary>
    /// Verifies that <see cref="DischargeService.DeleteDischargeAsync(Discharge)"/> successfully deletes a discharge
    /// when it exists in the repository.
    /// </summary>
    /// <param name="discharge">The discharge entity expected to be deleted; its identifier is used to invoke the repository delete operation.</param>
    [Test]
        public async Task DeleteDischargeAsync_WhenDischargeExists_DeletesDischarge()
        {
            // Arrange
            var dischargeId = ObjectId.GenerateNewId();
            var discharge = new Discharge { Id = dischargeId };
            _dischargeRepositoryMock.Setup(repo => repo.FindById(dischargeId)).ReturnsAsync(discharge);
    
            // Act
            await _dischargeService.DeleteDischargeAsync(discharge);
    
            // Assert
            _dischargeRepositoryMock.Verify(repo => repo.Delete(dischargeId), Times.Once);
        }

    //necesita el pocservice
    // [Test]
    // public async Task GetDischargeByIdAsync_WhenDischargeExists_ReturnsDischarge()
    // {
    //     // Arrange
    //     var dischargeId = ObjectId.GenerateNewId();
    //     var discharge = new Discharge { Id = dischargeId };
    //     _dischargeRepositoryMock.Setup(repo => repo.FindById(dischargeId)).ReturnsAsync(discharge);
    //
    //     // Act
    //     var result = await _dischargeService.GetDischargeByIdAsync(dischargeId);
    //
    //     // Assert
    //     Assert.That(result, Is.EqualTo(discharge));
    // }

    /// <summary>
    /// Verifies that <see cref="DischargeService.GetDischargeByIdAsync"/> throws a <see cref="NotFoundException"/> when the requested discharge is not found.
    /// </summary>
    [Test]
        public void GetDischargeByIdAsync_WhenExceptionOccurs_ReturnsNullAndLogsError()
        {
            // Arrange
            var dischargeId = ObjectId.GenerateNewId();
            // Act and assert
            Func<Task> act = async () => await _dischargeService.GetDischargeByIdAsync(dischargeId);
            Assert.ThrowsAsync<NotFoundException>(act);
        }

    /// <summary>
    /// Verifies that the discharge service returns the expected discharges retrieved from the repository when the underlying repository call completes successfully without exceptions.
    /// </summary>
    /// <returns>A <see cref="Task"/> representing the asynchronous test execution.</returns>
    [Test]
        public async Task GetDischargesAsync_WhenNoException_ReturnsDischarges()
        {
            // Arrange
            var discharge = TestUtilities.CreateValidDischarge();
            var discharges = new List<Discharge> { discharge };
            _dischargeRepositoryMock.Setup(repo => repo.FindAll()).ReturnsAsync(discharges);
    
            // Act
            var result = await _dischargeService.GetDischargesAsync();
    
            var resultList = result.ToList();
    
            // Assert
            Assert.That(resultList.First().Id, Is.EqualTo(discharges.First().Id));
        }

    //necesita el pocservice
    // [Test]
    // public async Task GetDischargeByPatientId_WhenDischargeExists_ReturnsDischarge()
    // {
    //     // Arrange
    //     var discharge = TestUtilities.CreateValidDischarge();
    //     _dischargeRepositoryMock.Setup(repo => repo.GetByPatientId(discharge.PatientId)).ReturnsAsync(discharge);
    //
    //     // Act
    //     var result = await _dischargeService.GetDischargeByPatientId(discharge.PatientId);
    //
    //     // Assert
    //     Assert.That(result?.Id, Is.EqualTo(discharge.Id));
    // }

    /// <summary>
    /// Verifies that <see cref="DischargeService.InsertDischarge"/> returns the inserted discharge
    /// when the underlying repository successfully completes the insertion and the entity can be retrieved by its identifier.
    /// </summary>
    [Test]
        public async Task InsertDischarge_WhenInsertionSuccessful_ReturnsInsertedDischarge()
        {
            // Arrange
            var dischargeId = ObjectId.GenerateNewId();
            var discharge = new Discharge { Id = dischargeId };
            _dischargeRepositoryMock.Setup(repo => repo.InsertOneAsync(discharge)).Returns(Task.CompletedTask);
            _dischargeRepositoryMock.Setup(repo => repo.FindById(dischargeId)).ReturnsAsync(discharge);
    
            // Act
            var result = await _dischargeService.InsertDischarge(discharge);
    
            // Assert
            Assert.That(result, Is.EqualTo(discharge));
        }

    /// <summary>
    /// Verifies that <see cref="DischargeService.InsertDischarge"/> handles repository insertion failures by propagating the exception thrown by the underlying data store.
    /// </summary>
    [Test]
        public void InsertDischarge_WhenInsertionFails_ReturnsNullAndLogsError()
        {
            // Arrange
            var discharge = new Discharge { Id = ObjectId.GenerateNewId() };
            var exception = new Exception("Failed to insert discharge");
            _dischargeRepositoryMock.Setup(repo => repo.InsertOneAsync(discharge)).ThrowsAsync(exception);
    
            // Assert
            Func<Task> act = async () => await _dischargeService.InsertDischarge(discharge);
            Assert.ThrowsAsync<Exception>(act);
        }

    /// <summary>
    /// Verifies that <see cref="DischargeService.GetDischargeByLocation"/> returns the discharge
    /// retrieved from the repository when a discharge exists for the specified patient location.
    /// </summary>
    [Test]
        public async Task GetDischargeByLocation_WhenDischargeExists_ReturnsDischarge()
        {
            // Arrange
            var location = new PatientLocation("UnitName", "Bed", "Room");
            var discharge = new Discharge();
            _dischargeRepositoryMock.Setup(repo => repo.GetDischargeByLocation(location)).ReturnsAsync(discharge);
    
            // Act
            var result = await _dischargeService.GetDischargeByLocation(location);
    
            // Assert
            Assert.That(result, Is.EqualTo(discharge));
        }

    /// <summary>
    /// Verifies that <see cref="IDischargeService.GetDischargeByLocation"/> returns <c>null</c> when the underlying discharge repository throws an exception while retrieving the discharge record for the specified patient location.
    /// </summary>
    [Test]
        public async Task GetDischargeByLocation_WhenExceptionOccurs_ReturnsNullAndLogsError()
        {
            // Arrange
            var location = new PatientLocation("UnitName", "Bed", "Room");
            var exception = new Exception("Failed to retrieve discharge by location");
            _dischargeRepositoryMock.Setup(repo => repo.GetDischargeByLocation(location)).ThrowsAsync(exception);
    
            // Act
            var result = await _dischargeService.GetDischargeByLocation(location);
    
            // Assert
            Assert.That(result, Is.Null);
        }

    // [Test]
    // public async Task GetDischargeByPointOfCareId_WhenDischargeExists_ReturnsDischarge()
    // {
    //     // Arrange
    //     var discharge = TestUtilities.CreateValidDischarge();
    //     _dischargeRepositoryMock.Setup(repo => repo.GetDischargeByPointOfCareId(discharge.PointOfCareId)).ReturnsAsync(discharge);
    //
    //     // Act
    //     var result = await _dischargeService.GetDischargeByPointOfCareId(discharge.PointOfCareId);
    //
    //     // Assert
    //     Assert.That(result, Is.EqualTo(discharge));
    // }

    /// <summary>
    /// Verifies that <see cref="DischargeService.GetDischargeByPointOfCareId"/> returns <c>null</c> when the underlying repository throws an exception while attempting to retrieve a discharge by its point-of-care identifier.
    /// </summary>
    [Test]
        public async Task GetDischargeByPointOfCareId_WhenExceptionOccurs_ReturnsNullAndLogsError()
        {
            // Arrange
            var poc = ObjectId.GenerateNewId();
            var exception = new Exception("Failed to retrieve discharge by PointOfCareId");
            _dischargeRepositoryMock.Setup(repo => repo.GetDischargeByPointOfCareId(poc)).ThrowsAsync(exception);
    
            // Act
            var result = await _dischargeService.GetDischargeByPointOfCareId(poc);
    
            // Assert
            Assert.That(result, Is.Null);
        }
}