using adas_core.Application.Exceptions;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Application.Subscriptions;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Utils;
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 DiagnosisService : IDiagnosisService
{
    private readonly ILocalAuditService _auditService;
    private readonly Lazy<ICalculatedObservationsService> _calculatedObservations;
    private readonly IClientMessageService _clientMessageService;
    private readonly IDiagnosisArchiveRepository _diagnosisArchiveRepository;

    private readonly List<string> _diagnosisCode = [];
    private readonly IDiagnosisRepository _diagnosisRepository;

    private readonly string _diagnosisSystem;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ILogger<DiagnosisService> _logger;
    private readonly Lazy<IPatientService> _patientService;
    private readonly ISubscribersService _subscribersService;
    private readonly IUnitService _unitService;


    public DiagnosisService(
        Lazy<IPatientService> patientService,
        IOptions<ApiSettings> apiSettings,
        IDiagnosisRepository diagnosisRepository,
        IDiagnosisArchiveRepository diagnosisArchiveRepository,
        ILogger<DiagnosisService> logger,
        IClientMessageService clientMessageService,
        ISubscribersService subscribersService,
        Lazy<ICalculatedObservationsService> calculatedObservations,
        IHttpContextAccessor httpContextAccessor,
        ILocalAuditService auditService,
        IUnitService unitService)
    {
        _patientService = patientService;
        _diagnosisRepository = diagnosisRepository;
        _diagnosisArchiveRepository = diagnosisArchiveRepository;
        _logger = logger;
        _clientMessageService = clientMessageService;
        _subscribersService = subscribersService;
        _calculatedObservations = calculatedObservations;
        _unitService = unitService;
        _httpContextAccessor = httpContextAccessor;
        _auditService = auditService;


        _diagnosisSystem = apiSettings.Value.DiagnosisSystem ?? "CUSTOM";

        if (apiSettings.Value.DiagnosisCode.Any())
            _diagnosisCode = apiSettings.Value.DiagnosisCode;
    }

    // private async Task SendBroadcast(PatientDiagnosis diagnosis)
    // {
    //     var patient = await _patientService.Value.FindById(diagnosis.PatientId);
    //     if (patient == null) return;
    //
    //
    //     var subscribers = _subscribersService.GetSubscribers().Where(s =>
    //         (s.SubscriptionType == SubscriptionType.Box && s.Box == patient.Bed &&
    //          s.Section == patient.UnitString) ||
    //         (s.SubscriptionType == SubscriptionType.Section && s.Section == patient.UnitString)).ToList();
    //     subscribers.ForEach(Action);
    //     return;
    //
    //     async void Action(WsSubscriber subscriber) =>
    //         await _clientMessageService.SendAsync(subscriber.Id, OperationType.Diagnosis, diagnosis);
    // }

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

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

        await DeleteByPatientId(id);
    }

    public async Task DeleteByPatientId(ObjectId id)
    {
        _logger.LogDebug("Delete Diagnoses by Patient Id {id}", id);
        var oldPatient = await _diagnosisRepository.GetByPatient(id);
        await _diagnosisRepository.DeleteByPatientId(id);
        await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldPatient, null);
    }

    public async Task<List<PatientDiagnosis>> GetByPatientId(ObjectId patientId)
    {
        var diagnosis = await _diagnosisRepository.GetByPatient(patientId);

        return diagnosis;
    }


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

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

    public async Task ProcessDiagnosisObservation(ApiRequest apiRequest, Patient patient)
    {
        _logger.LogDebug("INSERT DiagnosisObservation from obsservation");

        var time = apiRequest.ObservationData?.Time;
        if (time == null)
            _logger.LogWarning("Processing diagnosis without time. Set Datetime now {DateTimeNow}. ", DateTime.Now);

        var obs = new PatientDiagnosis
        {
            CodingSystem = _diagnosisSystem,
            Time = time ?? DateTime.Now,
            PatientId = patient.Id,
            MessageTime = apiRequest.MessageTime
        };

        if (apiRequest.Observations == null)
        {
            _logger.LogError("ApiRequest Observations null. ");
            return;
        }

        for (var i = 0; i <= apiRequest.Observations.Count - 1; i++)
        {
            var value = apiRequest.Observations[i].Value;

            var strValue = value.ToString() ?? "null";

            switch (apiRequest.Observations[i].Code)
            {
                case "272099008":
                    obs.Description = strValue;
                    break;

                case "1000000013":
                    obs.Label = strValue;
                    break;

                case "1000000014":
                    obs.Code = strValue;
                    break;

                case "394731006":
                    obs.State = strValue;
                    break;

                case "272125009":
                    obs.Category = strValue;
                    break;

                case "398201009":
                    if (DateTime.TryParse(apiRequest.Observations[i].Value.ToString(), out var startTime))
                        obs.StartTime = startTime;
                    break;

                case "397898000":
                    if (DateTime.TryParse(apiRequest.Observations[i].Value.ToString(), out var endTime))
                        obs.EndTime = endTime;
                    break;
            }
        }

        await InsertDiagnosis(obs);
    }

    public async Task SaveRequest(ApiRequest apiRequest, Patient? patient)
    {
        if (patient == null)
        {
            _logger.LogDebug("message:ApiRequest Diagnosis");
            if (string.IsNullOrEmpty(apiRequest.PatientNumber) &&
                string.IsNullOrEmpty(apiRequest.Location?.UnitName))
            {
                _logger.LogDebug("person and PointOfCare are nulls");
                throw new ApiRequestException("person and PointOfCare are nulls");
            }

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

            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;
        }

        var unitConfig = await _unitService.FindById(patient.UnitId);
        if (!Hl7Utils.ManageAutoAdt(unitConfig, null, _logger, "ORU Diagnosis")) return;

        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":

                // OBSERVATIONS
                if (apiRequest.Observation != null && apiRequest.Observations?.FirstOrDefault() == null)
                    apiRequest.Observations = [apiRequest.Observation];

                if (apiRequest.Observations != null)
                {
                    var obrcode = apiRequest.ObservationData?.Code;

                    if (apiRequest.ObservationData?.Value != null)
                        apiRequest.Observations.Add(new PatientObservation
                            { Value = apiRequest.ObservationData.Value });

                    if (obrcode != null && _diagnosisCode.Contains(obrcode))
                        _ = ProcessDiagnosisObservation(apiRequest, patient);
                }


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

    public async Task ProcessDiagnosis(List<PatientDiagnosis> diagnosis, Patient patient, DateTime messageTime)
    {
        _logger.LogDebug("INSERT DiagnosisObservation from diagnosis");

        foreach (var d in diagnosis)
        {
            d.PatientId = patient.Id;
            d.Time = messageTime;
            await InsertDiagnosis(d);
        }
    }

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

    public async Task<List<PatientDiagnosis>> GetByPatient(ObjectId id)
    {
        return await _diagnosisRepository.GetByPatient(id);
    }

    public async Task Insert(PatientDiagnosis diagnosis)
    {
        _logger.LogDebug("Insert {diagnosis}", diagnosis);
        var diag = await MapDiagnosis(diagnosis);
        if (diag != null)
        {
            await _diagnosisRepository.InsertOneAsync(diag);
            await SendBroadcast(diag);
        }
    }

    private async Task<PatientDiagnosis?> MapDiagnosis(PatientDiagnosis diagnosis)
    {
        var diagnosis2 = await _calculatedObservations.Value.Map(diagnosis);
        if (diagnosis2 == null) _logger.LogDebug("Mapping {diagnosis}: Ignored", diagnosis2);

        return diagnosis2;
    }

    private async Task SendBroadcast(PatientDiagnosis diagnosis)
    {
        var patient = await _patientService.Value.FindById(diagnosis.PatientId);
        if (patient == null) return;

        var displaySubscribers = _subscribersService.GetSubscribers().Where(s =>
            !s.Locations.IsNullOrEmpty() && s.Locations.Any(c =>
                c.UnitName == patient.Location.UnitName &&
                c.Bed == patient.Location.Bed &&
                c.Room == patient.Location.Room
            )).ToList();

        displaySubscribers.ForEach(Action);
        return;

        void Action(WsSubscriber subscriber)
        {
            _clientMessageService.SendAsync(subscriber.Id, OperationType.Diagnosis, diagnosis);
        }
    }

    public Task<IAsyncCursor<PatientDiagnosis>> FindByPatientIdAsync(ObjectId patientId)
    {
        return _diagnosisRepository.FindByPatientIdAsync(patientId);
    }

    public async Task InsertDiagnosis(PatientDiagnosis diagnosis)
    {
        try
        {
            _logger.LogDebug("Insert {diagnosis}", diagnosis);
            var diag = await MapDiagnosis(diagnosis);
            if (diag == null)
            {
                _logger.LogError("Mepped diagnosis is null. Diagnosis: {diagnosis}", diagnosis);
            }
            else
            {
                var dgdb = await _diagnosisRepository.FindByPatientIdAndCode(diag.PatientId, diag.Code,
                    diag.CodingSystem);

                if (dgdb != null)
                {
                    var auxDgdb = dgdb;
                    diag.Id = dgdb.Id;
                    diag.Time = dgdb.Time;
                    diag.UpdateDate = diagnosis.Time;
                    await _diagnosisRepository.UpdateOneAsync(diag.Id, diag);
                    await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, auxDgdb, dgdb);
                }
                else
                {
                    await _diagnosisRepository.InsertOneAsync(diagnosis);
                    await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, diagnosis);
                }

                _ = SendBroadcast(diagnosis);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError("Error inserting diagnosis. Diagnosis: {diagnosis}. Exception:{ex}", diagnosis, ex);
        }
    }
}