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.Enums;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using Moq;
using Options = Microsoft.Extensions.Options.Options;

namespace adas_core.Test.Services;

[TestFixture]
public class PointOfCareServiceTests
{
    /// <summary>
    /// Initializes the mock dependencies and test environment required for unit testing the <see cref="PointOfCareService"/>.
    /// Configures repository, service, cache, admission, unit, and HTTP context mocks, including a simulated authenticated user
    /// and cache behavior that invokes the supplied factory directly to return the produced <see cref="PointOfCare"/> instance.
    /// </summary>
    [SetUp]
        public void SetUp()
        {
            _pointOfCareRepositoryMock = new Mock<IPointOfCareRepository>();
            _unitServiceMock = new Mock<IUnitService>();
            _admissionServiceMock = new Mock<IAdmissionService>();
            _cacheServiceMock = new Mock<ICacheService>();
            _cacheServiceMock
                .Setup(c => c.GetOrSetObjectAsync(
                    It.IsAny<string>(),
                    It.IsAny<Func<Task<PointOfCare?>>>(),
                    It.IsAny<TimeSpan?>(),
                    It.IsAny<CancellationToken>()))
                .Returns((string _, Func<Task<PointOfCare?>> factory, TimeSpan? _, CancellationToken _) => factory());
    
            var loggerMock = new Mock<ILogger<PointOfCareService>>();
            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);
            _pointOfCareService = new PointOfCareService(
                loggerMock.Object,
                _pointOfCareRepositoryMock.Object,
                new Lazy<IPatientService>(Mock.Of<IPatientService>),
                new Lazy<IUnitService>(() => _unitServiceMock.Object),
                Mock.Of<ISubscribersService>(),
                Mock.Of<Lazy<IClientMessageService>>(),
                new Lazy<IAdmissionService>(() => _admissionServiceMock.Object),
                _httpContextAccessorMock.Object,
                _auditServiceMock.Object,
                _cacheServiceMock.Object,
                Options.Create(new CacheSettings())
            );
        }

    private PointOfCareService _pointOfCareService = null!;
    private Mock<IPointOfCareRepository> _pointOfCareRepositoryMock = null!;
    private Mock<IUnitService> _unitServiceMock = null!;
    private Mock<ICacheService> _cacheServiceMock = null!;
    private Mock<IAdmissionService> _admissionServiceMock = null!;
    private readonly Mock<IHttpContextAccessor> _httpContextAccessorMock = new();
    private readonly Mock<ILocalAuditService> _auditServiceMock = new();

    /// <summary>
    /// Verifies that the <see cref="PointOfCareService.Delete"/> method successfully deletes a <c>PointOfCare</c>
    /// when invoked with a valid <see cref="ObjectId"/> whose associated admission is <c>null</c>, ensuring
    /// the underlying repository's delete operation is invoked.
    /// </summary>
    [Test]
        public async Task Delete_ValidObjectId_DeletesPointOfCare()
        {
            // Arrange
            var id = ObjectId.GenerateNewId();
            _pointOfCareRepositoryMock.Setup(m => m.FindByIdAllConfig(id))
                .ReturnsAsync(new PointOfCare { Id = id, AdmissionId = null });
            _pointOfCareRepositoryMock.Setup(m => m.Delete(id)).Verifiable();
    
            // Act
            await _pointOfCareService.Delete(id);
    
            // Assert
            _pointOfCareRepositoryMock.Verify();
        }

    /// <summary>
    /// Verifies that the <see cref="PointOfCareService.Update"/> method successfully updates a valid
    /// <see cref="PointOfCare"/> instance by delegating the operation to the underlying repository.
    /// </summary>
    /// <returns>A <see cref="Task"/> representing the asynchronous test execution.</returns>
    [Test]
        public async Task Update_ValidPointOfCare_UpdatesPointOfCare()
        {
            // Arrange
            var pointOfCare = new PointOfCare { Id = ObjectId.GenerateNewId(), UnitName = "TestUnit" };
            _pointOfCareRepositoryMock.Setup(m => m.Update(pointOfCare)).Verifiable();
    
            // Act
            await _pointOfCareService.Update(pointOfCare);
    
            // Assert
            _pointOfCareRepositoryMock.Verify();
        }

    /// <summary>
    /// Verifies that <see cref="PointOfCareService.GetAll"/> returns the complete list of point of care records provided by the repository when invoked with no arguments.
    /// </summary>
    [Test]
        public async Task GetAll_NoArguments_ReturnsListOfPointOfCare()
        {
            // Arrange
            var pointOfCareList = new List<PointOfCare> { new(), new() };
            _pointOfCareRepositoryMock.Setup(m => m.GetAll()).ReturnsAsync(pointOfCareList);
    
            // Act
            var result = await _pointOfCareService.GetAll();
    
            // Assert
            Assert.That(result, Is.EqualTo(pointOfCareList));
        }

    /// <summary>
    /// Verifies that <see cref="PointOfCareService.UpdateConfiguration"/> throws a <see cref="ConflictException"/> when invoked with a valid id and configuration, ensuring the service surfaces conflict conditions during the update operation.
    /// </summary>
    [Test]
        public void UpdateConfiguration_ValidIdAndConfiguration_InvokesRepositoryUpdateConfiguration()
        {
            // Arrange
            var id = ObjectId.GenerateNewId();
            var configuration = new PointOfCareConfiguration();
    
            // Act & Assert
            Assert.That(_pointOfCareService, Is.Not.Null);
            Assert.That(_pointOfCareRepositoryMock, Is.Not.Null);
    
            Func<Task> act = async () => await _pointOfCareService.UpdateConfiguration(id, configuration);
            Assert.ThrowsAsync<ConflictException>(act);
        }

    /// <summary>
    /// Verifies that <c>FindById</c> returns the expected <see cref="PointOfCare"/> when a valid identifier is provided.
    /// </summary>
    [Test]
        public async Task FindById_ValidId_ReturnsPointOfCare()
        {
            // Arrange
            var id = ObjectId.GenerateNewId();
            var expectedPointOfCare = new PointOfCare { Id = id };
            _pointOfCareRepositoryMock.Setup(m => m.FindByIdAllConfig(id)).ReturnsAsync(expectedPointOfCare);
    
            // Act
            var result = await _pointOfCareService.FindById(id);
    
            // Assert
            Assert.That(result, Is.EqualTo(expectedPointOfCare));
        }

    // [Test]
    // public async Task FindByUnit_ValidUnit_ReturnsListOfPointOfCare()
    // {
    //     // Arrange
    //     var id = ObjectId.GenerateNewId();
    //     var pointOfCare = new PointOfCare { Id = id, UnitId = ObjectId.GenerateNewId() };
    //     var unit = new Unit { Id = pointOfCare.UnitId, Name = "TestUnit" };
    //     var expectedPointOfCareList = new List<PointOfCare>
    //     {
    //         new PointOfCare { Unit = unit },
    //         new PointOfCare { Unit = unit },
    //     };
    //     _pointOfCareRepositoryMock.Setup(m => m.FindByUnit(unit)).ReturnsAsync(expectedPointOfCareList);
    //
    //     // Act
    //     var result = await _pointOfCareService.FindByUnit(unit);
    //
    //     // Assert
    //     Assert.That(result, Is.EqualTo(expectedPointOfCareList));
    // }

    /// <summary>
    /// Verifies that <see cref="PointOfCareService.FindByUnitAndStatus"/> returns the expected list of point-of-care records when invoked with a valid unit identifier and status.
    /// </summary>
    [Test]
        public async Task FindByUnitAndStatus_ValidUnitIdAndStatus_ReturnsListOfPointOfCare()
        {
            // Arrange
            var unitId = ObjectId.GenerateNewId();
            var pocStatus = StatusEnum.PointOfCare.Available;
            var expectedPointOfCareList = new List<PointOfCare>
            {
                new(),
                new()
            };
            _pointOfCareRepositoryMock.Setup(m => m.FindByUnitAndStatus(unitId, pocStatus, false))
                .ReturnsAsync(expectedPointOfCareList);
    
            // Act
            var result = await _pointOfCareService.FindByUnitAndStatus(unitId, pocStatus);
    
            // Assert
            Assert.That(result, Is.EqualTo(expectedPointOfCareList));
        }

    [Test]
    public void CheckNextAdmission_ValidPatientLocation_UpdatesPoCStatus_AndCallsUpdate()
    {
        // Arrange
        var patientLocationId = ObjectId.GenerateNewId();

        var pocToCheck = new PointOfCare
        {
            Id = patientLocationId,
            AdmissionId = null, // Forzamos la rama que busca la siguiente admisión
            Status = StatusEnum.PointOfCare.Locked // Locked → no debe actualizar. Cambia para probar actualización:
                                                    // Usa Available o Reserved para que se ejecute el update.
        };

        // Para que ejecute la lógica de actualización, cambiamos el estado inicial:
        pocToCheck.Status = StatusEnum.PointOfCare.Available;

        var nextAdmission = new Admission { Id = ObjectId.GenerateNewId() };

        // 1) Cache: debe ejecutar el factory y devolver el PoC
        _cacheServiceMock
            .Setup(c => c.GetOrSetObjectAsync(
                It.IsAny<string>(),
                It.IsAny<Func<Task<PointOfCare?>>>(),
                It.IsAny<TimeSpan?>(),
                It.IsAny<CancellationToken>()))
            .Returns((string _, Func<Task<PointOfCare?>> factory, TimeSpan? _, CancellationToken _) => factory());

        // 2) Repo: FindById (usado por el factory del caché)
        _pointOfCareRepositoryMock
            .Setup(m => m.FindById(patientLocationId))
            .ReturnsAsync(pocToCheck);

        // 3) AdmissionService (ruta AdmissionId == null → buscar siguiente)
        _admissionServiceMock
            .Setup(m => m.GetAdmissionByPointOfCareId(pocToCheck.Id))
            .ReturnsAsync([nextAdmission]);

        // 4) Update(poc) → queremos esperar a que se invoque
        var updatedSignal = new ManualResetEventSlim(false);
        _pointOfCareRepositoryMock
            .Setup(m => m.Update(It.IsAny<PointOfCare>()))
            .Callback<PointOfCare>(_ => updatedSignal.Set())
            .Returns(Task.CompletedTask);

        // Update llama internamente a FindById otra vez (en Update)
        _pointOfCareRepositoryMock
            .Setup(m => m.FindById(pocToCheck.Id))
            .ReturnsAsync(pocToCheck);

        // 5) Ejecutar (async void) y esperar a que se dispare el update
        _pointOfCareService.CheckNextAdmission(patientLocationId);

        // Espera razonable a que termine la operación asíncrona interna
        var completed = updatedSignal.Wait(TimeSpan.FromSeconds(1));
        Assert.That(completed, Is.True, "CheckNextAdmission no disparó Update a tiempo.");

        // Assert
        // a) Se llamó a FindById (vía caché)
        _pointOfCareRepositoryMock.Verify(m => m.FindById(patientLocationId), Times.AtLeastOnce);

        // b) Se llamó a Update con el PoC actualizado: Status Reserved + Admission asignada
        _pointOfCareRepositoryMock.Verify(m => m.Update(
            It.Is<PointOfCare>(p =>
                p.Id == pocToCheck.Id &&
                p.Status == StatusEnum.PointOfCare.Reserved &&
                p.AdmissionId == nextAdmission.Id &&
                p.Admission == nextAdmission)
        ), Times.Once);

        // c) NO verifiques FindByIdAllConfig: en CheckNextAdmission no se usa esa ruta
    }

    /// <summary>
    /// Tests that the service's FindByRoom method, when called with a valid room, returns the collection provided by the repository and invokes the repository's FindByRoom exactly once.
    /// </summary>
    [Test]
        public async Task FindByRoom_ValidRoom_CallsRepositoryFindByRoom()
        {
            // Arrange
            const string room = "TestRoom";
            var expectedPointOfCares = new List<PointOfCare>();
            _pointOfCareRepositoryMock.Setup(m => m.FindByRoom(room)).ReturnsAsync(expectedPointOfCares);
    
            // Act
            var result = await _pointOfCareService.FindByRoom(room);
    
            // Assert
            Assert.That(result, Is.EqualTo(expectedPointOfCares));
            _pointOfCareRepositoryMock.Verify(m => m.FindByRoom(room), Times.Once);
        }

    /// <summary>
    /// Verifies that the <see cref="PointOfCareService.FindByBed"/> method correctly delegates to the repository's FindByBed method when a valid bed identifier is provided, returning the expected collection of point of care records.
    /// </summary>
    [Test]
        public async Task FindByBed_ValidBed_CallsRepositoryFindByBed()
        {
            // Arrange
            const string bed = "TestBed";
            var expectedPointOfCares = new List<PointOfCare>();
            _pointOfCareRepositoryMock.Setup(m => m.FindByBed(bed)).ReturnsAsync(expectedPointOfCares);
    
            // Act
            var result = await _pointOfCareService.FindByBed(bed);
    
            // Assert
            Assert.That(result, Is.EqualTo(expectedPointOfCares));
            _pointOfCareRepositoryMock.Verify(m => m.FindByBed(bed), Times.Once);
        }

}