using adas_core.Application.Exceptions;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.Responses;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;

namespace adas_core.Application.Services;

public class MedicineService(
    IMedicineRepository medicineRepository,
    ITreatmentService treatmentService,
    IOptions<ApiSettings> apiSettings,
    ILogger<MedicineService> logger,
    IHttpContextAccessor httpContextAccessor,
    ILocalAuditService auditService)
    : IMedicineService
{
    private readonly List<string> _notesIndicatingMedication = apiSettings.Value.NotesIndicatingMedication ?? [];

    public async Task<List<Medicine>> GetAll()
    {
        return await medicineRepository.GetAll();
    }

    public async Task<Medicine?> GetByCode(string code)
    {
        return await medicineRepository.GetMedicine(code);
    }

    public async Task<List<Medicine>> GetByCodeOrNote(List<string> codeNote)
    {
        return await medicineRepository.GetMedicineByCodeOrNote(codeNote);
    }

    public async Task<Medicine?> GetByName(string name)
    {
        return await medicineRepository.GetMedicineByName(name);
    }

    public async Task<IEnumerable<Medicine>> GetMedicinesOfTreatments(IEnumerable<PatientTreatment?> treatments)
    {
        var totalMedicines = new List<Medicine>();

        try
        {
            foreach (var treatment in treatments)
            {
                if (treatment == null)
                    continue;

                var medicines = new List<Medicine>();

                foreach (var code in treatment.RequestedGiveCodes)
                {
                    var medicineList = await medicineRepository.GetMedicineByCodeOrNote([code.Identifier]);
                    var medicine = medicineList.FirstOrDefault();
                    //Cant retrieve from medicine list check for parental nutrition and if is not and any note indicate that is medication it is added
                    //to collection

                    if (medicine == null)
                    {
                        var parentalNutritionMedicine = await CalculateParentalNutritionMedicine(treatment);
                        if (parentalNutritionMedicine != null)
                        {
                            medicines.Add(parentalNutritionMedicine);
                            continue;
                        }

                        var isMedicine = treatment.Notes.Any(n => _notesIndicatingMedication.Contains(n.Comment));
                        if (isMedicine)
                            medicines.Add(new Medicine
                            {
                                Codes = [code.Identifier],
                                Name = code.Text
                            });
                    }
                    else if (!medicine.Type.Contains("Nutrition"))
                    {
                        medicines.Add(medicine);
                    }
                }

                if (!string.IsNullOrEmpty(treatment.RequestedGiveTreatment))
                    try
                    {
                        medicines =
                        [
                            new Medicine
                            {
                                Name = treatment.RequestedGiveTreatment,
                                Type = medicines.FindAll(t => t.Type.Any())
                                    .Select(m => m.Type.Aggregate((x, y) => x + "," + y)).Distinct()
                                    .ToList(),
                                Codes = medicines.FindAll(t => t.Codes.Any())
                                    .Select(m => m.Codes.Aggregate((x, y) => x + "," + y)).Distinct()
                                    .ToList(),
                                Group = medicines.FindAll(t => t.Group.Any())
                                    .Select(m => m.Group.Aggregate((x, y) => x + "," + y)).Distinct()
                                    .ToList(),
                                Notes = medicines.FindAll(t => t.Notes.Any())
                                    .Select(m => m.Notes.Aggregate((x, y) => x + "," + y)).Distinct()
                                    .ToList()
                            }
                        ];
                    }
                    catch (Exception ex)
                    {
                        logger.LogError("Error Getting Medicines from Treatment. {medicines} . Exception {ex}",
                            string.Join(",", medicines), ex);
                    }

                totalMedicines.AddRange(medicines);
            }
        }
        catch (Exception e)
        {
            logger.LogError("Error get Medicines Of Treatments: {eMessage} {eStackTrace}", e.Message, e.StackTrace);
        }

        return totalMedicines.AsEnumerable();
    }

    public async Task<IEnumerable<Medicine>> GetActiveMedicinesByPatient(ObjectId patientId)
    {
        var activeTreatments = await treatmentService.GetActiveTreatmentsByPatient(patientId);
        return await GetMedicinesOfTreatments(activeTreatments);
    }


    public async Task<PaginationResponse<Medicine>> GetPaginatedMedicines(PaginationFilter filter)
    {
        var result = medicineRepository.GetPaginatedMedicines(filter);

        var count = await result.CountDocumentsAsync();

        var data = await result.Skip((filter.PageNumber - 1) * filter.PageSize)
            .Limit(filter.PageSize)
            .ToCursorAsync();


        var dataList = await data.ToListAsync();

        return new PaginationResponse<Medicine>(dataList, filter.PageNumber, filter.PageSize, count);
    }


    public async Task<Medicine?> GetMedicineById(ObjectId medicineId)
    {
        return await medicineRepository.GetMedicineById(medicineId) ??
               throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
    }

    public async Task<Medicine?> PostMedicine(Medicine medicine)
    {
        var result = await medicineRepository.PostMedicine(medicine) ??
                     throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, result);
        return result;
    }

    public async Task<Medicine?> UpdateMedicine(Medicine medicine)
    {
        var oldMedicine = GetMedicineById(medicine.Id);
        var newMedicine = await medicineRepository.UpdateMedicine(medicine);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldMedicine, newMedicine);
        return newMedicine;
    }

    public async Task DeleteMedicineById(ObjectId medicineId)
    {
        var oldMedicine = GetMedicineById(medicineId);
        await medicineRepository.DeleteMedicineById(medicineId);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldMedicine, null);
    }

    public async Task<List<string>> GetAllTypes()
    {
        try
        {
            var bsonDocuments = await medicineRepository.GetDistinctFieldDataQuery("type").ToListAsync();
            var result = bsonDocuments.Select(doc => doc["type"].AsString).ToList();
            return result;
        }
        catch (Exception)
        {
            return [];
        }
    }

    public async Task<List<string>> GetAllGroups()
    {
        try
        {
            var bsonDocuments = await medicineRepository.GetDistinctFieldDataQuery("group").ToListAsync();
            var result = bsonDocuments.Select(doc => doc["group"].AsString).ToList();
            return result;
        }
        catch (Exception)
        {
            return [];
        }
    }

    public async Task<List<string>> GetAllNames()
    {
        try
        {
            var bsonDocuments = await medicineRepository.GetDistinctFieldDataQuery("name").ToListAsync();
            var result = bsonDocuments.Select(doc => doc["name"].AsString).ToList();
            return result;
        }
        catch (Exception)
        {
            return [];
        }
    }

    public async Task<List<string>> GetAllCodes()
    {
        try
        {
            var bsonDocuments = await medicineRepository.GetDistinctFieldDataQuery("codes").ToListAsync();
            var result = bsonDocuments.Select(doc => doc["codes"].AsString).ToList();
            return result;
        }
        catch (Exception)
        {
            return [];
        }
    }

    //Exclusive for H12O, not in calculatedObservations of H12O to dont repeat code in multiple places.
    private Task<Medicine?> CalculateParentalNutritionMedicine(PatientTreatment treatment)
    {
        Medicine? medicine = null;

        try
        {
            if (treatment is { Notes: not null } && treatment.Notes.FirstOrDefault(n => n.Comment == "NPT") != null)
            {
                medicine = new Medicine
                {
                    Name = treatment.Notes.FirstOrDefault(n => n.CommentType == "formularybaseformulation")?.Comment ??
                           "UNKNOWN"
                };

                if (treatment.Notes is { Count: > 0 }) medicine.Notes = treatment.Notes.Select(t => t.Comment).ToList();


                if (treatment.Notes.FirstOrDefault(n =>
                        n.Comment is "LÍPIDOS NEONATALES AL 20%" or "LÍPIDOS NEONATALES AL 20% CON...") != null)
                    medicine.Type = [MedicineEnum.Types.ParenteralNutritionLipids.ToString()];
                else
                    medicine.Type = [MedicineEnum.Types.ParenteralNutrition.ToString()];
            }
        }
        catch (Exception e)
        {
            logger.LogError("Error calculate Parental Nutrition Medicine: {eMessage} {eStackTrace}", e.Message,
                e.StackTrace);
        }

        return Task.FromResult(medicine);
    }
}