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.AppSettings;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using Moq;
using static NUnit.Framework.Is;
using Options = Microsoft.Extensions.Options.Options;

namespace adas_core.Test.Services;

/// <summary>
/// Contains unit tests for the <see cref="MedicineService"/> class, verifying its behavior and contracts.
/// </summary>
/// <!-- aidoc:v1 sig=6e8ca9d -->
[TestFixture]
public class MedicineServiceTest
{
    /// <summary>
    /// Initializes the test dependencies for <see cref="MedicineService"/> unit tests by creating mocks for <see cref="IMedicineRepository"/>, <see cref="ITreatmentService"/>, and <see cref="ILogger{MedicineService}"/>, then constructing the service under test with the required collaborators.
    /// </summary>
    /// <!-- aidoc:v1 sig=dee8bf2 body=c01c921 -->
    [SetUp]
    public void Setup()
    {
        _medicineRepositoryMock = new Mock<IMedicineRepository>();
        _treatmentServiceMock = new Mock<ITreatmentService>();

        _optionsApiSettings = Options.Create(_apiSettings);

        _logger = new Mock<ILogger<MedicineService>>();

        _medicineService = new MedicineService(
            _medicineRepositoryMock.Object,
            _treatmentServiceMock.Object,
            _optionsApiSettings,
            _logger.Object,
            _httpContextAccessor.Object,
            _auditService.Object);
    }

    private MedicineService _medicineService;

    private Mock<IMedicineRepository> _medicineRepositoryMock;
    private Mock<ITreatmentService> _treatmentServiceMock;
    private readonly Mock<IHttpContextAccessor> _httpContextAccessor = new();
    private readonly Mock<ILocalAuditService> _auditService = new();

    private readonly ApiSettings _apiSettings = new()
    {
        NotesIndicatingMedication = ["NoteMedication"]
    };

    private IOptions<ApiSettings> _optionsApiSettings;

    private Mock<ILogger<MedicineService>> _logger;

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

    /// <summary>
    /// Verifies that <see cref="MedicineService.GetMedicinesOfTreatments"/> returns an empty result (count of 0) when <see cref="IMedicineRepository.GetMedicineByCodeOrNote"/> yields no <see cref="Medicine"/> items for the supplied <see cref="PatientTreatment"/> entries.
    /// </summary>
    /// <exception cref="ArgumentNullException">Thrown when the local <see cref="List{PatientTreatment}"/> collection is null.</exception>
    /// <!-- aidoc:v1 sig=6587029 body=89ace4d -->
    /// <!-- aidoc-review:v1 severity=medium kind=extra_exception
    ///      "ArgumentNullException is unreachable: patientTreatment is initialized to an empty list (List<PatientTreatment> patientTreatment = []) immediately before the null check, so the throw can never execute in practice." -->
    [Test]
    public async Task GetMedicinesOfTreatments_Null_Medicine_Return_0()
    {
        var medicineList = new List<Medicine>();

        List<PatientTreatment> patientTreatment = [];
        if (patientTreatment == null) throw new ArgumentNullException(nameof(patientTreatment));

        _medicineRepositoryMock.Setup(m => m.GetMedicineByCodeOrNote(It.IsAny<List<string>>()))
            .ReturnsAsync(medicineList);

        var result = await _medicineService.GetMedicinesOfTreatments(patientTreatment);

        Assert.That(result.Count(), EqualTo(0));
    }

    /// <summary>
    /// Verifies that <see cref="MedicineService.GetMedicinesOfTreatments"/> returns medicines when the supplied
    /// <see cref="List{PatientTreatment}"/> contains codes matching medicines registered in the repository.
    /// </summary>
    /// <!-- aidoc:v1 sig=9b6ab1e body=dfdcc25 -->
    [Test]
    public async Task GetMedicinesOfTreatments_Medicines_Return_Medicines()
    {
        var treatment =
            new PatientTreatment
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = PatientId,
                OrderControl = OrderControlType.Nw,
                PlacerOrder = new Entity { EntityIdentifier = "100", NamespaceId = "CareVue" },
                FillerOrder = new Entity { EntityIdentifier = "100", NamespaceId = "CareVue" },
                RequestedGiveCodes = [new Code { Identifier = "12345" }],
                OrderStatus = "A",
                OrderTime = DateTime.Now,
                Notes = [],
                Routes = []
            };

        var treatmentList = new List<PatientTreatment>
        {
            treatment
        };

        var medicine =
            new Medicine
            {
                Codes = ["12345"],
                Group = [MedicineEnum.Group.DoubleSignature.ToString()],
                Type = [MedicineEnum.Types.MuscleRelaxant.ToString()],
                Name = "cisataracurio"
            };


        var medicineList = new List<Medicine>
        {
            medicine
        };

        _medicineRepositoryMock.Setup(m => m.GetMedicineByCodeOrNote(It.IsAny<List<string>>()))
            .ReturnsAsync(medicineList);

        var result = await _medicineService.GetMedicinesOfTreatments(treatmentList);

        Assert.That(result.Count(), GreaterThan(0));
    }

    /// <summary>
    /// Tests that <see cref="MedicineService.GetMedicinesOfTreatments"/> returns medicines for a <see cref="PatientTreatment"/> when the medicine repository returns no results for <see cref="MedicineRepository.GetMedicineByCodeOrNote"/>, verifying that the returned medicines contain the identifier and text extracted from <see cref="PatientTreatment.RequestedGiveCodes"/>.
    /// </summary>
    /// <!-- aidoc:v1 sig=1cd9fc2 body=15ec2c4 -->
    [Test]
    public async Task GetMedicinesOfTreatments_Not_Get_Medicines_Return_Note_Medicines()
    {
        var treatment =
            new PatientTreatment
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = PatientId,
                OrderControl = OrderControlType.Nw,
                PlacerOrder = new Entity { EntityIdentifier = "100", NamespaceId = "CareVue" },
                FillerOrder = new Entity { EntityIdentifier = "100", NamespaceId = "CareVue" },
                RequestedGiveCodes = [new Code { Identifier = "12345", Text = "CodeText" }],
                OrderStatus = "A",
                OrderTime = DateTime.Now,
                Notes = [new Note { Comment = "NoteMedication" }],
                Routes = []
            };

        var treatmentList = new List<PatientTreatment>
        {
            treatment
        };

        _medicineRepositoryMock.Setup(m => m.GetMedicineByCodeOrNote(It.IsAny<List<string>>())).ReturnsAsync([]);

        var result = await _medicineService.GetMedicinesOfTreatments(treatmentList);
        using (Assert.EnterMultipleScope())
        {
            var medicines = result as Medicine[] ?? result.ToArray();
            Assert.That(medicines.Count(), GreaterThan(0));
            Assert.That(medicines.FirstOrDefault()?.Codes.FirstOrDefault(), EqualTo("12345"));
            Assert.That(medicines.FirstOrDefault()?.Name, EqualTo("CodeText"));
        };
    }

    /// <summary>
    /// Verifies that <see cref="MedicineService.GetMedicinesOfTreatments"/> falls back to constructing medicines from the <see cref="PatientTreatment.RequestedGiveCodes"/> text when the medicine repository returns no medicines matching the requested codes.
    /// </summary>
    /// <!-- aidoc:v1 sig=2beba14 body=6e04063 -->
    [Test]
    public async Task GetMedicinesOfTreatments_Not_Get_Medicines_Not_Code_identifier_Return_Note_Medicines()
    {
        var treatment =
            new PatientTreatment
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = PatientId,
                OrderControl = OrderControlType.Nw,
                PlacerOrder = new Entity { EntityIdentifier = "100", NamespaceId = "CareVue" },
                FillerOrder = new Entity { EntityIdentifier = "100", NamespaceId = "CareVue" },
                RequestedGiveCodes = [new Code { Text = "CodeText" }],
                OrderStatus = "A",
                OrderTime = DateTime.Now,
                Notes = [new Note { Comment = "NoteMedication" }],
                Routes = []
            };

        var treatmentList = new List<PatientTreatment>
        {
            treatment
        };


        _medicineRepositoryMock.Setup(m => m.GetMedicineByCodeOrNote(It.IsAny<List<string>>())).ReturnsAsync([]);

        var result = await _medicineService.GetMedicinesOfTreatments(treatmentList);
        using (Assert.EnterMultipleScope())
        {
            var medicines = result as Medicine[] ?? result.ToArray();
            Assert.That(medicines.Count(), GreaterThan(0));
            Assert.That(medicines.FirstOrDefault()?.Codes.FirstOrDefault(), EqualTo(string.Empty));
            Assert.That(medicines.FirstOrDefault()?.Name, EqualTo("CodeText"));
        };
    }

    /// <summary>
    /// Verifies that <see cref="MedicineService.GetMedicinesOfTreatments"/> returns an NPT <see cref="Medicine"/> with type <see cref="MedicineEnum.Types.ParenteralNutrition"/> when the <see cref="PatientTreatment"/> notes contain the "NPT" formularybaseformulation comment.
    /// </summary>
    /// <!-- aidoc:v1 sig=186f001 body=182af67 -->
    [Test]
    public async Task CalculateParentalNutritionMedicine_Notes_NPT_Return_NPT_Medicine()
    {
        var treatment =
            new PatientTreatment
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = PatientId,
                OrderControl = OrderControlType.Nw,
                PlacerOrder = new Entity { EntityIdentifier = "100", NamespaceId = "CareVue" },
                FillerOrder = new Entity { EntityIdentifier = "100", NamespaceId = "CareVue" },
                RequestedGiveCodes = [new Code { Text = "CodeText" }],
                OrderStatus = "A",
                OrderTime = DateTime.Now,
                Notes = [new Note { Comment = "NPT", CommentType = "formularybaseformulation" }],
                Routes = []
            };

        var treatmentList = new List<PatientTreatment>
        {
            treatment
        };


        _medicineRepositoryMock.Setup(m => m.GetMedicineByCodeOrNote(It.IsAny<List<string>>())).ReturnsAsync([]);

        var result = await _medicineService.GetMedicinesOfTreatments(treatmentList);
        using (Assert.EnterMultipleScope())
        {
            var medicines = result as Medicine[] ?? result.ToArray();
            Assert.That(medicines.Count(), GreaterThan(0));
            Assert.That(medicines.FirstOrDefault()?.Codes.FirstOrDefault(), EqualTo(null));
            Assert.That(medicines.FirstOrDefault()?.Name, EqualTo("NPT"));
            Assert.That(MedicineEnum.Types.ParenteralNutrition.ToString(),
                EqualTo(medicines.FirstOrDefault()?.Type.FirstOrDefault()));
        };
    }

    /// <summary>
    /// Verifies that when a <see cref="PatientTreatment"/> includes notes indicating "NPT" parenteral nutrition and a lipid formulation comment, <see cref="MedicineService.GetMedicinesOfTreatments"/> resolves the treatment into a <see cref="Medicine"/> named "NPT" of type <see cref="MedicineEnum.Types.ParenteralNutritionLipids"/> with no associated code, even when <see cref="IMedicineRepository.GetMedicineByCodeOrNote"/> returns an empty list.
    /// </summary>
    /// <!-- aidoc:v1 sig=bfdb93a body=11a42b3 -->
    [Test]
    public async Task CalculateParentalNutritionMedicine_Notes_NPTL_Return_NPTL_Medicine()
    {
        var treatment =
            new PatientTreatment
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = PatientId,
                OrderControl = OrderControlType.Nw,
                PlacerOrder = new Entity { EntityIdentifier = "100", NamespaceId = "CareVue" },
                FillerOrder = new Entity { EntityIdentifier = "100", NamespaceId = "CareVue" },
                RequestedGiveCodes = [new Code { Text = "CodeText" }],
                OrderStatus = "A",
                OrderTime = DateTime.Now,
                Notes =
                [
                    new Note { Comment = "NPT", CommentType = "formularybaseformulation" },
                    new Note { Comment = "LÍPIDOS NEONATALES AL 20%" }
                ],
                Routes = []
            };

        var treatmentList = new List<PatientTreatment>
        {
            treatment
        };


        _medicineRepositoryMock.Setup(m => m.GetMedicineByCodeOrNote(It.IsAny<List<string>>())).ReturnsAsync([]);

        var result = await _medicineService.GetMedicinesOfTreatments(treatmentList);
        using (Assert.EnterMultipleScope())
        {
            var medicines = result as Medicine[] ?? result.ToArray();
            Assert.That(medicines.Count(), GreaterThan(0));
            Assert.That(medicines.FirstOrDefault()?.Codes.FirstOrDefault(), EqualTo(null));
            Assert.That(medicines.FirstOrDefault()?.Name, EqualTo("NPT"));
            Assert.That(MedicineEnum.Types.ParenteralNutritionLipids.ToString(),
                EqualTo(medicines.FirstOrDefault()?.Type.FirstOrDefault()));
        };
    }
}