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.MongoModels;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;

namespace adas_core.Application.Services;

public class RecordingAlertService(
    Lazy<IPatientService> patientService,
    IConfigObservationService configObservationService,
    IRecordingAlertRepository recordingAlertRepository,
    IRecordingAlertArchiveRepository recordingAlertArchiveRepository,
    ILogger<RecordingAlertService> logger,
    IClientMessageService clientMessageService,
    ISubscribersService subscribersService,
    IHttpContextAccessor httpContextAccessor,
    ILocalAuditService auditService)
    : IRecordingAlertService
{
    public async Task Archive(Patient patient)
    {
        await ArchiveByPatientId(patient.Id);
    }

    public async Task ArchiveByPatientId(ObjectId id)
    {
        logger.LogDebug("Archive Recording Alerts by Patient Id {id}", id);
        using (var cursor = await recordingAlertRepository.FindByPatientIdAsync(id))
        {
            while (await cursor.MoveNextAsync())
                foreach (var current in cursor.Current)
                    await recordingAlertArchiveRepository.InsertOneAsync(current);
        }

        await DeleteByPatientId(id);
    }

    public async Task DeleteByPatientId(ObjectId id)
    {
        logger.LogDebug("Delete Recording Alerts by Patient Id {id}", id);
        await recordingAlertRepository.DeleteByPatientId(id);
    }

    public async Task<List<PatientRecordingAlert>> FindLastRecordingAlert(ObjectId patientId, int num = 2)
    {
        return await recordingAlertRepository.AggregatedPatientLastObservations(patientId, num);
    }


    public Task SaveRequestAsync(ApiRequest apiRequest)
    {
        return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); }));
    }

    public async Task SaveRequest(ApiRequest apiRequest)
    {
        if (string.IsNullOrEmpty(apiRequest.PatientNumber) && string.IsNullOrEmpty(apiRequest.Location?.UnitName) &&
            string.IsNullOrEmpty(apiRequest.Location?.Bed))
        {
            logger.LogDebug("Patient and Location are nulls in apiRequest RecordingAlerts");
            return;
        }

        logger.LogDebug("patientNumber:  {apiRequestpatientNumber} location:  {apiRequestlocation}",
            apiRequest.PatientNumber, apiRequest.Location);
        logger.LogDebug("RequestType:  {apiRequesttype}", apiRequest.Type);

        switch (apiRequest.Type)
        {
            /*
             * ORU_R01 - Unsolicited transmission of an observation message
             * ORU_R40 - Unsolicited transmission of an alert observation message
             */
            case "ORU_R01":
            case "ORU_R40":
            case "RecordingAlert":


                var patient = await patientService.Value.FindPatientByApiRequest(apiRequest);


                if (patient == null)
                {
                    // NO PATIENTS OR LOCATIONS WERE FOUND 
                    logger.LogWarning(
                        "Observation for patient: {apiRequestpatientNumber} with location: {apiRequestlocation} not found, ignoring",
                        apiRequest.PatientNumber, apiRequest.Location);
                    return;
                }


                // Recording Alerts

                if (apiRequest.RecordingAlert != null && apiRequest.RecordingAlerts?.FirstOrDefault() == null)
                    apiRequest.RecordingAlerts = [apiRequest.RecordingAlert];


                if (apiRequest.RecordingAlerts != null)
                {
                    logger.LogDebug("INSERT {apiRequestrecordingAlerts} OBSERVATIONS",
                        apiRequest.RecordingAlerts.Count);
                    foreach (var obs in apiRequest.RecordingAlerts)
                    {
                        obs.PatientId = patient.Id;
                        obs.Id = ObjectId.GenerateNewId();
                        if (obs.Time == DateTime.MinValue) obs.Time = DateTime.UtcNow;
                        await InsertRecordingAlert(obs);
                    }
                }

                break;
            default:
                logger.LogWarning("ApiRequest type {apiRequesttype} sis not valid for Observations", apiRequest.Type);
                throw new ApiRequestException("ApiRequest type " + apiRequest.Type + " is not valid for Observations");
        }
    }

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

    private async Task InsertRecordingAlert(PatientRecordingAlert recAlert)
    {
        logger.LogDebug("Insert {recAlert}", recAlert);
        await recordingAlertRepository.InsertOneAsync(recAlert);
        await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, recAlert);
        await SendObsBroadcast(recAlert);
        await DoRetentionActions(recAlert);
    }

    private async Task SendObsBroadcast(BasePatientObservation recAlert)
    {
        if (recAlert.Name == null) return;

        var type = recAlert is PatientRecordingAlert ? OperationType.RecordingAlert : OperationType.Observation;


        var patient = await patientService.Value.FindById(recAlert.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, type, recAlert);
    }

    private async Task DoRetentionActions(PatientRecordingAlert recAlert)
    {
        var result = await configObservationService.RetentionActions(recAlert);
        if (result is not { RetentionPolicyValue: not null } || recAlert.Name == null) return;
        switch (result.RetentionPolicy)
        {
            case RetentionPolicy.DeleteOlderDays:
                await recordingAlertRepository.DeleteOlderDaysAsync(recAlert.Name, result.RetentionPolicyValue.Value);
                break;
            case RetentionPolicy.DeleteOlderNumber:
                await recordingAlertRepository.DeleteOlderNumberAsync(recAlert.Name, result.RetentionPolicyValue.Value);
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
    }
}