using System.Security.Claims;
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;
using adas_core.Domain.Models.MongoModels;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using Moq;

namespace adas_core.Test.Services;

/// <summary>
/// Provides a unit test class for testing the functionality of the UnitService.
/// </summary>
/// <remarks>
/// This class is intended to contain test methods that validate the behavior and correctness of the UnitService.
/// </remarks>
public class UnitServiceTest
{
    private readonly Mock<ILocalAuditService> _auditServiceMock = new();
    private readonly Mock<IHttpContextAccessor> _httpContextAccessorMock = new();
    private Mock<IClientMessageService> _clientMessageServiceMock = null!;
    private Mock<ILogger<UnitService>> _loggerMock = null!;
    private Mock<IMasterListServiceFactory> _masterListServiceFactoryMock = null!;
    private Mock<IPatientService> _patientServiceMock = null!;
    private Mock<IPointOfCareService> _pointOfCareServiceMock = null!;
    private Mock<ISubscribersService> _subscribersServiceMock = null!;
    private Mock<IUnitRepository> _unitRepositoryMock = null!;
    private UnitService _unitService = null!;

    /// <summary>
    /// Initializes mocked dependencies and a configured <see cref="UnitService"/> instance for each test,
    /// including a mock <see cref="HttpContext"/> with a test user claim.
    /// </summary>
    [SetUp]
    public void SetUp()
    {
        _unitRepositoryMock = new Mock<IUnitRepository>();
        _patientServiceMock = new Mock<IPatientService>();
        _loggerMock = new Mock<ILogger<UnitService>>();
        _clientMessageServiceMock = new Mock<IClientMessageService>();
        _subscribersServiceMock = new Mock<ISubscribersService>();
        _masterListServiceFactoryMock = new Mock<IMasterListServiceFactory>();
        _pointOfCareServiceMock = new Mock<IPointOfCareService>();
        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);
        _unitService = new UnitService(
            _unitRepositoryMock.Object,
            new Lazy<IPatientService>(() => _patientServiceMock.Object),
            _loggerMock.Object,
            _masterListServiceFactoryMock.Object,
            _subscribersServiceMock.Object,
            new Lazy<IClientMessageService>(() => _clientMessageServiceMock.Object),
            _pointOfCareServiceMock.Object,
            _httpContextAccessorMock.Object,
            _auditServiceMock.Object
        );
    }

    /// <summary>
    /// Verifies that the unit service returns all units retrieved from the repository, including the correct count and content.
    /// </summary>
    [Test]
    public async Task GetAll_ShouldReturnAllUnits()
    {
        // Arrange
        var units = new List<Unit> { new() { Id = ObjectId.GenerateNewId() } };
        _unitRepositoryMock.Setup(repo => repo.GetAll()).ReturnsAsync(units);

        // Act
        var result = await _unitService.GetAll();

        // Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result.Count, Is.EqualTo(1));
        Assert.That(result, Is.EqualTo(units));
    }

    /// <summary>
    /// Verifies that the Get method on the unit service returns the expected unit when a valid identifier is provided.
    /// </summary>
    [Test]
    public async Task Get_ShouldReturnUnitById()
    {
        // Arrange
        var unitId = ObjectId.GenerateNewId();
        var unit = new Unit { Id = unitId };
        _unitRepositoryMock.Setup(repo => repo.FindById(unitId)).ReturnsAsync(unit);

        // Act
        var result = await _unitService.Get(unitId.ToString());

        // Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result, Is.EqualTo(unit));
    }

    /// <summary>
    /// Verifies that the Get method retrieves a unit by name when the provided identifier is not a valid ObjectId, matching against either the unit's Title or Name.
    /// </summary>
    [Test]
    public async Task Get_ShouldReturnUnitByName_WhenIdIsNotObjectId()
    {
        // Arrange
        var unitName = "TestUnit";
        var units = new List<Unit>
            {
                new() { Title = unitName },
                new() { Name = unitName }
            };
        _unitRepositoryMock.Setup(repo => repo.GetAll()).ReturnsAsync(units);

        // Act
        var result = await _unitService.Get(unitName);

        // Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result?.Title ?? result?.Name, Is.EqualTo(unitName));
    }


    /// <summary>
    /// Verifies that _unitService" returns all units retrieved from the repository,
    /// ensuring the result is not null, contains the expected number of units, and matches the source data.
    /// </summary>
    [Test]
    public async Task GetAllUnits_ShouldReturnAllUnits()
    {
        // Arrange
        var units = new List<Unit>
            {
                new() { Id = ObjectId.GenerateNewId(), Name = "Unit1" },
                new() { Id = ObjectId.GenerateNewId(), Name = "Unit2" }
            };

        _unitRepositoryMock.Setup(repo => repo.GetAll()).ReturnsAsync(units);

        // Act
        var result = await _unitService.GetAll();

        // Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result.Count(), Is.EqualTo(units.Count));
        Assert.That(result, Is.EquivalentTo(units));
    }

    /// <summary>
    /// Verifies that _unitService.FindById" returns the matching <see cref="Unit"/> when the repository locates it by id.
    /// </summary>
    [Test]
    public async Task GetUnitById_ShouldReturnUnit_WhenFound()
    {
        // Arrange
        var unitId = ObjectId.GenerateNewId();
        var unit = new Unit { Id = unitId, Name = "Unit1" };

        _unitRepositoryMock.Setup(repo => repo.FindById(unitId)).ReturnsAsync(unit);

        // Act
        var result = await _unitService.FindById(unitId);

        // Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result, Is.EqualTo(unit));
    }

    /// <summary>
    /// Verifies that <c>GetByName</c> returns the matching unit when a unit with the specified name exists in the repository.
    /// </summary>
    [Test]
    public async Task GetUnitByName_ShouldReturnUnit_WhenFound()
    {
        // Arrange
        var unitName = "TestUnit";
        var expectedUnit = new Unit { Id = ObjectId.GenerateNewId(), Name = unitName };

        _unitRepositoryMock.Setup(repo => repo.FindByName(unitName)).ReturnsAsync(expectedUnit);

        // Act
        var result = await _unitService.GetByName(unitName);

        // Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result, Is.EqualTo(expectedUnit));
    }

    /// <summary>
    /// Verifies that <c>FindByPatientId</c> returns the associated unit when the patient is found in an active (in-use) point of care.
    /// </summary>
    [Test]
    public async Task FindUnitByPatientId_ShouldReturnUnit_WhenPatientFoundInActivePoC()
    {
        // Arrange
        var patientId = ObjectId.GenerateNewId();
        var unitId = ObjectId.GenerateNewId();
        var pocId = ObjectId.GenerateNewId();
        var patient = new Patient { Id = patientId, PointOfCareId = pocId, UnitId = unitId };
        var unit = new Unit
        {
            Id = unitId,
            PointOfCares = [new PointOfCare { Id = pocId, UnitId = unitId, Status = StatusEnum.PointOfCare.InUse }]
        };

        _unitRepositoryMock.Setup(repo => repo.FindById(unitId)).ReturnsAsync(unit);
        _patientServiceMock.Setup(service => service.FindById(patientId, false)).ReturnsAsync(patient);

        // Act
        var result = await _unitService.FindByPatientId(patientId);

        // Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result?.PointOfCares?.Any(c => c.Id == patient.PointOfCareId && c.UnitId == patient.UnitId),
            Is.True);
    }


    /// <summary>
    /// Verifies that the unit service returns the matching units retrieved from the repository when a call is made to find units by the specified master list identifier and type.
    /// </summary>
    [Test]
    public async Task FindUnitsByMasterListId_ShouldReturnUnits_WhenUnitsFound()
    {
        // Arrange
        var masterListId = ObjectId.GenerateNewId();
        var masterListType = MasterListType.AltableOptionList;
        var units = new List<Unit> { new(), new() };

        _unitRepositoryMock.Setup(repo => repo.FindByMasterListId(masterListId, masterListType)).ReturnsAsync(units);

        // Act
        var result = await _unitService.FindUnitsByMasterListId(masterListId, masterListType);

        // Assert
        if (result != null)
        {
            var actual = result.ToList();
            Assert.That(actual, Is.Not.Null);
            Assert.That(actual, Is.EqualTo(units));
        }
    }

    /// <summary>
    /// Verifies that the unit service returns the matching units when the repository contains units
    /// whose point of care matches the specified patient location.
    /// </summary>
    [Test]
    public async Task FindByLocation_ShouldReturnUnits_WhenUnitsFound()
    {
        // Arrange
        var location = new PatientLocation("TestUnit", "TestBed", "TestRoom");
        var units = new List<Unit>
            {
                new()
                {
                    PointOfCares = [new PointOfCare { UnitName = "TestUnit", Bed = "TestBed" }]
                }
            };
        _unitRepositoryMock.Setup(repo => repo.GetAll()).ReturnsAsync(units);

        // Act
        var result = await _unitService.FindByLocation(location);

        // Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result?.Count, Is.EqualTo(1));
        Assert.That(result?.First().PointOfCares?.First().UnitName, Is.EqualTo("TestUnit"));
        Assert.That(result?.First().PointOfCares?.First().Bed, Is.EqualTo("TestBed"));
    }

    /// <summary>
    /// Verifies that FindByLocation returns an empty list when no units match the specified patient location.
    /// </summary>
    [Test]
    public async Task FindByLocation_ShouldReturnEmptyList_WhenNoUnitsFound()
    {
        // Arrange
        var location = new PatientLocation("NonExistentUnit", "NonExistentBed", "NonExistentRoom");
        _unitRepositoryMock.Setup(repo => repo.GetAll()).ReturnsAsync([]);

        // Act
        var result = await _unitService.FindByLocation(location);

        // Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result?.Count, Is.EqualTo(0));
    }

    /// <summary>
    /// Verifies that the unit service returns the expected unit when a valid unit identifier is provided.
    /// </summary>
    /// <returns>A task that represents the asynchronous test execution.</returns>
    [Test]
    public async Task FindById_ShouldReturnUnit_WhenValidIdProvided()
    {
        // Arrange
        var unitId = ObjectId.GenerateNewId();
        var expectedUnit = new Unit { Id = unitId };
        _unitRepositoryMock.Setup(repo => repo.FindById(unitId)).ReturnsAsync(expectedUnit);

        // Act
        var result = await _unitService.FindById(unitId);

        // Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result, Is.EqualTo(expectedUnit));
    }

    /// <summary>
    /// Verifies that the unit service's FindById method returns null when invoked with a null identifier,
    /// ensuring graceful handling of null input without throwing or returning a default entity.
    /// </summary>
    [Test]
    public async Task FindById_ShouldReturnNull_WhenNullIdProvided()
    {
        // Arrange

        // Act
        var result = await _unitService.FindById(null);

        // Assert
        Assert.That(result, Is.Null);
    }

    /// <summary>
    /// Verifies that FindByName retrieves and returns the expected <c>Unit</c> when a valid unit name is provided.
    /// </summary>
    [Test]
    public async Task FindByName_ShouldReturnUnit_WhenValidNameProvided()
    {
        // Arrange
        var unitName = "Unit1";
        var expectedUnit = new Unit { Name = unitName };
        _unitRepositoryMock.Setup(repo => repo.FindByName(unitName)).ReturnsAsync(expectedUnit);

        // Act
        var result = await _unitService.FindByName(unitName);

        // Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result, Is.EqualTo(expectedUnit));
    }


    /// <summary>
    /// Verifies that the unit service correctly delegates the insert operation to the repository and returns the inserted unit entity unchanged.
    /// </summary>
    [Test]
    public async Task InsertOne_ShouldReturnInsertedUnit()
    {
        // Arrange
        var unit = new Unit { Id = ObjectId.GenerateNewId(), Name = "TestUnit" };
        _unitRepositoryMock.Setup(repo => repo.InsertOneUnit(unit)).ReturnsAsync(unit);

        // Act
        var result = await _unitService.InsertOne(unit);

        // Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result, Is.EqualTo(unit));
    }
}