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.Filter;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;

namespace adas_core.Application.Services;

public class TreatmentService(
    ITreatmentRepository treatmentRepository,
    ITreatmentArchiveRepository treatmentArchiveRepository,
    IPatientService patientService,
    IConfigObservationService configObservationService,
    ILogger<TreatmentService> logger,
    IClientMessageService clientMessageService,
    ISubscribersService subscribersService,
    Lazy<ICalculatedObservationsService> calculatedObservationsService,
    IUnitService unitService,
    IHttpContextAccessor httpContextAccessor,
    ILocalAuditService auditService)
    : ITreatmentService
{
    public async Task<IEnumerable<PatientTreatment>> GetTreatmentsByPatientId(ObjectId id)
    {
        return await treatmentRepository.GetByPatientId(id);
    }

    public async Task Insert(PatientTreatment treatment)
    {
        logger.LogDebug("Insert {treatment}", treatment);
        var mappedTreatment = await MapTreatment(treatment);
        if (mappedTreatment != null)
        {
            await treatmentRepository.InsertOneAsync(mappedTreatment);
            await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, mappedTreatment);
            _ = SendBroadcast(mappedTreatment, OperationType.Treatment);
        }
    }

    public async Task<bool> DeleteByPatientId(ObjectId id)
    {
        var treatmentToDelete = await FindByPatientId(id) ??
                                throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);

        if (!treatmentToDelete.Any())
            return true;

        logger.LogDebug("Delete Treatments by Patient Id {id}", id);
        var result = await treatmentRepository.DeleteByPatientId(id);
        if (!result) throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, treatmentToDelete, null);
        return result;
    }

    public async Task Archive(Patient patient)
    {
        await ArchiveByPatientId(patient.Id);
    }

    public async Task ArchiveByPatientId(ObjectId id)
    {
        logger.LogDebug("ArchiveB Treatments by Patient Id PatientId {id}", id);
        using (var cursor = await FindByPatientIdAsync(id))
        {
            while (await cursor.MoveNextAsync())
                foreach (var current in cursor.Current)
                    await treatmentArchiveRepository.InsertOneAsync(current);
        }

        await DeleteByPatientId(id);
    }

    public async Task<bool> UpdateTreatment(PatientTreatment patientTreatment)
    {
        var oldTreatment = await treatmentRepository.GetById(patientTreatment.Id) ??
                           throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
        logger.LogDebug("Updating Treatment {treatment}", patientTreatment.ToJson());
        var result = await treatmentRepository.Update(patientTreatment);
        if (!result) throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldTreatment, patientTreatment);

        _ = SendBroadcast(patientTreatment, OperationType.UpdateTreatment);

        return result;
    }

    public async Task<IAsyncCursor<PatientTreatment>> FindByPatientIdAsync(ObjectId patientId)
    {
        return await treatmentRepository.FindByPatientIdAsync(patientId);
    }

    public async Task<IEnumerable<PatientTreatment>> FindByPatientId(ObjectId patientId)
    {
        return await treatmentRepository.FindByPatientId(patientId);
    }

    public async Task<List<PatientTreatment>> GetBolusTreatments(ObjectId patientId)
    {
        return await treatmentRepository.FindBolusTreatments(patientId);
    }

    public async Task SaveRequestAsync(ApiRequest apiRequest)
    {
        await SaveRequest(apiRequest);
    }

    public async Task SaveRequest(ApiRequest apiRequest)
    {
        if (string.IsNullOrEmpty(apiRequest.PatientNumber))
        {
            logger.LogDebug("Patient is null");
            throw new ApiRequestException("Patient is null");
        }

        logger.LogDebug("patientNumber:  {apiRequestpatientNumber}", apiRequest.PatientNumber);
        var unitConfig = await unitService.FindByName(apiRequest.Location?.UnitName);
        if (unitConfig != null)
        {
            if (!unitConfig.Configuration.AutoAdt && !apiRequest.RequestFromPanel)
            {
                logger.LogWarning("Unit: {UnitName} with auto adt set to false can't manage auto ADT",
                    apiRequest.Location?.UnitName);
                return;
            }
        }
        else
        {
            logger.LogWarning("Unit not found in Mapping list: {UnitName} can't manage auto ADT",
                apiRequest.Location?.UnitName);
            return;
        }

        try
        {
            switch (apiRequest.Type)
            {
                /*
                 * OMP EVENT
                 * OMP_O09 - Pharmacy/treatment order
                 * ORM_O01 - Order message
                 * RAS_O17 - Pharmacy/treatment administration
                 */
                case "OMP_O09":
                case "ORM_O01":
                case "RAS_O17":
                    var patient = await patientService.FindByPatientNumber(apiRequest.PatientNumber);
                    if (patient == null)
                    {
                        logger.LogDebug(
                            "Ignore treatment: {apiRequest.treatments} for patient: {apiRequest.patientNumber}, patient not found",
                            apiRequest.Treatment, apiRequest.PatientNumber);
                        return;
                    }

                    apiRequest.Treatments ??= [];

                    if (apiRequest.Treatment != null) apiRequest.Treatments.Add(apiRequest.Treatment);

                    foreach (var treatment in apiRequest.Treatments)
                    {
                        treatment.PatientId = patient.Id;
                        treatment.MessageTime = apiRequest.MessageTime;
                        await Insert(treatment);
                    }

                    break;
                default:
                    throw new ApiRequestException("ApiRequest type " + apiRequest.Type +
                                                  " is not valid for Treatments");
            }
        }
        catch (Exception ex)
        {
            logger.LogError("ERROR SAVING REQUEST: {exMessage}  trace:  {exStackTrace}", ex.Message, ex.StackTrace);
            throw;
        }
    }

    /*
     * Return active treatments of patient PatientType active = NW, canceled= DC
     */
    public async Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
    {
        var treatments = await treatmentRepository.GetByPatientId(id);

        var activeTreatments = treatments
            .Where(IsValidTreatment)
            .GroupBy(t => t.PlacerOrder?.EntityIdentifier)
            .Select(GetMostRecentActiveTreatment)
            .Where(t => t != null)
            .AsEnumerable();

        return activeTreatments;
    }

    public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
    {
        await treatmentRepository.UpdateManyObjectId(nameId, id, oldId);

        var treatments = await treatmentRepository.FindByPatientId(id);
        foreach (var treat in treatments) _ = SendBroadcast(treat, OperationType.UpdateTreatment);
    }


    public async Task<PaginationResponse<PatientTreatment>> GetPaginatedTreatments(PaginationFilter filter)
    {
        var result = treatmentRepository.GetPaginatedTreatments(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<PatientTreatment>(dataList, filter.PageNumber, filter.PageSize, count);
    }

    public async Task<List<PatientTreatment>> GetActiveTreatmentsByPatientIdAndOrder(ObjectId patientId, string order)
    {
        return await treatmentRepository.GetActiveTreatmentsByPatientIdAndOrder(patientId, order);
    }


    public async Task DeleteById(ObjectId id)
    {
        var oldTreatment = await treatmentRepository.GetById(id) ??
                           throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldTreatment, null);
        await treatmentRepository.DeleteAsync(id);
    }

    private async Task<PatientTreatment?> MapTreatment(PatientTreatment treatment)
    {
        var treatment2 = await calculatedObservationsService.Value.Map(treatment);
        if (treatment2 == null) return null;
        var treatment3 = await configObservationService.Map(treatment2);
        if (treatment3 == null) logger.LogDebug("Mapping {treatment}: Ignored", treatment);

        return treatment;
    }


    private async Task SendBroadcast(PatientTreatment treatment, OperationType operationType)
    {
        var patient = await patientService.FindById(treatment.PatientId);

        if (patient?.PointOfCareId == null) return;

        var subscribers = subscribersService.GetSubscribers()
            .Where(s => s.LocationIds.Contains(patient.PointOfCareId.Value)).ToList();


        foreach (var subscriber in subscribers)
            await clientMessageService.SendAsync(subscriber.Id, operationType, treatment);
    }

    private static bool IsValidTreatment(PatientTreatment treatment)
    {
        if (treatment.PlacerOrder == null)
            return false;

        var currentTime = DateTime.UtcNow;
        if (treatment.EndTime != null && currentTime.CompareTo(treatment.EndTime) > 0)
            return false;
        if (treatment.StartTime != null && currentTime.CompareTo(treatment.StartTime) < 0)
            return false;

        return treatment.OrderControl != OrderControlType.Dc;
    }

    private static PatientTreatment? GetMostRecentActiveTreatment(IGrouping<string?, PatientTreatment> group)
    {
        return group
            .Where(t => t.OrderControl is OrderControlType.Nw or OrderControlType.Xo)
            .OrderByDescending(t => t.OrderTime)
            .FirstOrDefault();
    }
}