using System.Net; using System.Net.Http.Headers; using System.Text; 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.DTO; using adas_core.Domain.Models.MongoModels; using adas_core.Domain.Models.Recording; using adas_core.Domain.Models.Responses; using adas_core.Domain.Utils; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Serilog; using Patient = adas_core.Domain.Models.MongoModels.Patient; namespace adas_core.Application.Services; public class RecordingService : IRecordingService { private readonly IAuthService _authService; private readonly IClientMessageService _clientMessageService; private readonly HttpClient _httpClient; private readonly ILogger _logger; private readonly Lazy _patientService; private readonly IPublisherService _publisherService; private readonly RabbitMqSettings _rabbitMqSettings; private readonly RecordingSettings _recordingSettings; private readonly bool _startRecordingWithoutPatientNumber; private readonly ISubscribersService _subscribersService; private readonly string? _url; private AccessGrant? _accessGrant; public RecordingService(IOptions rabbitMqSettings, IOptions recordingSettings, ILogger logger, IHttpClientFactory httpClientFactory, IPublisherService publisherService, IAuthService authService, IOptions apiSettings, IClientMessageService clientMessageService, ISubscribersService subscribersService, Lazy patientService) { _logger = logger; _httpClient = httpClientFactory.CreateClient(); _accessGrant ??= new AccessGrant(); _rabbitMqSettings = rabbitMqSettings.Value; _recordingSettings = recordingSettings.Value ?? throw new Exception("RecordingSettings must be defined on appSettings"); _httpClient.Timeout = new TimeSpan(0, 0, _recordingSettings.HttpClientTimeout); _url = _recordingSettings.RecordingApiUrl; _publisherService = publisherService; _authService = authService; _clientMessageService = clientMessageService; _subscribersService = subscribersService; _patientService = patientService; RecordingQueueName = _rabbitMqSettings.RecordingQueue; ErrorRecordingQueueName = $"{RecordingQueueName}_Error"; _startRecordingWithoutPatientNumber = apiSettings.Value.StartRecordingWithoutPatientNumber; } private string RecordingQueueName { get; } private string ErrorRecordingQueueName { get; } public async Task SendCancelRecordingToRecordingApi(Patient patient, PointOfCare poc) { var token = await _authService.GetToken(); if (string.IsNullOrEmpty(_url) || string.IsNullOrEmpty(token)) throw new Exception( "send recording data to recording api require RecordingOrApiUrl defined and valid token"); var body = JsonConvert.SerializeObject(GenerateRecordingData(patient, poc, DateTime.MinValue, null, null, null, null, AlarmEnum.Severity.None, "")); var request = new HttpRequestMessage(HttpMethod.Post, $"{_url}/videos/delete-video-in-progress") //las fechas no se mandan { Content = new StringContent(body, Encoding.UTF8) }; request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {token}"); request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); var response = await _httpClient.SendAsync(request); _logger.LogDebug( "send cancel to recording api status code:{responseStatusCode} url: {url} body: {body}", response.StatusCode, _url, body); return response.IsSuccessStatusCode; } public async Task SendRecordingData(Patient patient, PointOfCare poc, ManualRecording manualRecording, bool start) { await SendRecordingDataToQueue(patient, poc, manualRecording.Recording?.StartRecordingTime, manualRecording.Recording?.StopRecordingTime, null, null, AlarmEnum.Severity.None, null, start); } public async Task SendAutoRecordingData(Patient patient, PointOfCare poc, RecordingData automaticRecording) { try { if (!Enum.TryParse(automaticRecording.AlarmName, out var alarmName)) return false; await SendRecordingDataToQueue(patient, poc, automaticRecording.StartRecordingTime, automaticRecording.StopRecordingTime, automaticRecording.EventTime, alarmName, automaticRecording.Severity, automaticRecording.AlarmDescription, type: AlarmEnum.Type.Auto); return true; } catch (Exception ex) { Log.Error("Exception sending automatic recording data. Exception: {ex}", ex); return false; } } public async Task SendRecordingDataToQueue(Patient patient, PointOfCare poc, DateTime? date, DateTime? endDate, DateTime? eventDate, AlarmEnum.Name? alarmName, AlarmEnum.Severity severity, string? alarmDescription, bool start = true, AlarmEnum.Type type = AlarmEnum.Type.Manual) { if (!_startRecordingWithoutPatientNumber && string.IsNullOrEmpty(patient.PatientNumber)) { _logger.LogError( "Error sending recording data to queue. Patient number is null or empty. PatientId: {patientId}", patient.PatientId); return; } if (poc.Status != StatusEnum.PointOfCare.InUse) throw new Exception($"Box does not contain patients:->{JsonConvert.SerializeObject(poc)}"); if (string.IsNullOrEmpty(alarmDescription)) alarmDescription = "UNKNOWN"; var retryCount = 1; while (retryCount <= _rabbitMqSettings.MaxRetries) { try { ApiRequest recordingRequest = new() { Recording = GenerateRecordingData(patient, poc, date, endDate, eventDate, null, alarmName, severity, alarmDescription, type) }; _logger.LogDebug("Send recording data patient {patient}", patient); await _publisherService.SendMessage(recordingRequest, RecordingQueueName); if (recordingRequest.Recording != null) _ = SendRecordingBroadcast([recordingRequest.Recording]); return; } catch (HttpRequestException ce) { retryCount++; _logger.LogError("Connection error sending recording data to queue. {ce} Retrying...", ce.Message); } catch (Exception e) { _logger.LogError("Error sending recording data to queue.{eError} {eMessage} ", e, e.Message); await _publisherService.SendMessage(e.Message, ErrorRecordingQueueName); throw; } if (retryCount == _rabbitMqSettings.MaxRetries) await _publisherService.SendMessage("Error: Connection retries exceeded.", ErrorRecordingQueueName); } } public async Task?> GetRecordings(int roomId) { var retryCount = 1; while (retryCount <= _rabbitMqSettings.MaxRetries) try { var token = await _authService.GetToken(); if (string.IsNullOrEmpty(_recordingSettings.RecordingApiUrl) || string.IsNullOrEmpty(token)) { Log.Warning( "Not recording url defined at recordingSettings or empty token, ignoring recordings"); return null; } var request = new HttpRequestMessage(HttpMethod.Get, $"{_recordingSettings.RecordingApiUrl}/videos/allInProgress/{roomId}"); request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {token}"); var response = await _httpClient.SendAsync(request); _logger.LogDebug( "GetByCodeSysAndCode recordings to recording api status code:{responseStatusCode} url: {url} body", response.StatusCode, _url); var responseContent = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { if (response.StatusCode == HttpStatusCode.Unauthorized) { _accessGrant = null; _logger.LogError( "error connecting to RecordingOrApi url:{requestRequestUri} errorCode: {responseStatusCode} error {responseContent} token: {}", request.RequestUri, response.StatusCode, response.Content, _accessGrant?.AccessToken ?? "not exist"); } else if (response.StatusCode == HttpStatusCode.NotFound) { return []; } else { _logger.LogError( "error connecting to RecordingOrApi url:{requestRequestUri} errorCode: {responseStatusCode} error {responseContent}", request.RequestUri, response.StatusCode, response.Content); return []; } if (retryCount == _rabbitMqSettings.MaxRetries) await _publisherService.SendMessage("Error: Connection retries exceeded.", ErrorRecordingQueueName); return []; } var records = JsonConvert.DeserializeObject>(responseContent); return records; } catch (HttpRequestException ce) { retryCount++; _logger.LogError( "Connection error sending recording data to queue. {ce} Retrying... {retryCount} of {maxRetrys}", ce.Message, retryCount, _rabbitMqSettings.MaxRetries); //return new List(); } catch (Exception e) { _logger.LogError("Error getting recordings room: {roomId} error: {eMessage} ", roomId, e.Message); await _publisherService.SendMessage(e.Message, ErrorRecordingQueueName); return []; } if (retryCount == _rabbitMqSettings.MaxRetries) { await _publisherService.SendMessage("Error: Connection retries exceeded.", ErrorRecordingQueueName); return null; } return []; } public async Task SaveRequestAsync(ApiRequest apiRequest) { var retryCount = 1; while (retryCount <= _rabbitMqSettings.MaxRetries) { try { var token = await _authService.GetToken(); _logger.LogDebug("sending recording to Recording or api"); var recordingRequest = apiRequest.Recording; var request = new HttpRequestMessage(HttpMethod.Post, $"{_recordingSettings.RecordingApiUrl}/videos/save-recording-data") { Content = new StringContent(JsonConvert.SerializeObject(recordingRequest), Encoding.UTF8) }; request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {token}"); request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); var response = await _httpClient.SendAsync(request); _logger.LogDebug("Save Request to recording api status code:{responseStatusCode}", response.StatusCode); var responseContent = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { if (response.StatusCode == HttpStatusCode.Unauthorized) _accessGrant = null; _logger.LogError( "error connecting to RecordingOrApi url:{requestRequestUri} errorCode: {responseStatusCode} error {responseContent}", request.RequestUri, response.StatusCode, response.Content); retryCount++; } else { // Response y en Data esta el videoDTO var resp = JsonConvert.DeserializeObject>(responseContent); var video = resp?.Data; if (video == null) return; RecordingData recording = new() { StartRecordingTime = video.StartDate, StopRecordingTime = video.EndDate, EventTime = video.Date, RoomId = video.RoomId, AlarmType = video.Alarm, Store = video.Store, Status = video.VideoStatus.ToString(), Patient = new Domain.Models.Recording.Patient { FirstName = apiRequest.Patient?.FirstName ?? apiRequest.Recording?.Patient?.FirstName ?? string.Empty, Id = apiRequest.PatientNumber ?? apiRequest.Recording?.Patient?.Id ?? string.Empty, LastName = apiRequest.Patient?.LastName ?? apiRequest.Recording?.Patient?.LastName ?? string.Empty } }; _ = SendRecordingBroadcast([recording]); return; } } catch (HttpRequestException ce) { retryCount++; _logger.LogError("Connection error sending recording data to queue. {ce} Retrying...", ce.Message); await _publisherService.SendMessage(apiRequest, ErrorRecordingQueueName); } catch (Exception e) { _logger.LogError("Error sending recording request to Recording or {eMessage}", e.Message); await _publisherService.SendMessage(apiRequest, ErrorRecordingQueueName); await Task.FromException(e); } if (retryCount > _rabbitMqSettings.MaxRetries) await _publisherService.SendMessage( $"Error: Connection retries exceeded. for api request: {apiRequest}", ErrorRecordingQueueName); } } public Task SaveRequest(ApiRequest apiRequest) { //return Task.CompletedTask; throw new NotImplementedException(); } private RecordingData? GenerateRecordingData(Patient patient, PointOfCare poc, DateTime? startDate, DateTime? endDate, DateTime? eventTime, int? minToExpired, AlarmEnum.Name? alarmName, AlarmEnum.Severity severity, string alarmDescription, AlarmEnum.Type alarm = AlarmEnum.Type.Manual) { if (patient.PatientNumber == null) { _logger.LogError("Error Generating Recording Data . Patient number is null: {patient}", patient); return null; } var roomId = poc.Configuration?.Id; if (roomId == null) { _logger.LogError("Error Generating Recording Data on roomId"); return null; } var patientRecording = new Domain.Models.Recording.Patient { Id = patient.PatientNumber, FirstName = patient.Person?.FirstName ?? "X", LastName = $"{patient.Person?.SecondName} {patient.Person?.LastName}".TrimEnd() }; if (!int.TryParse(roomId.ToString(), out var roomIdParsed)) { _logger.LogError("Error Generating Recording Data on roomId parse: {roomId}", roomId); return null; } var recordingData = new RecordingData { Patient = patientRecording, RoomId = roomIdParsed, StartRecordingTime = startDate, StopRecordingTime = endDate ?? (minToExpired != null ? DateTime.UtcNow.AddMinutes(minToExpired.Value) : null), EventTime = eventTime, AlarmType = alarm, Severity = severity, AlarmName = alarmName.HasValue ? EnumUtils.GetDescription(alarmName) : null, AlarmDescription = alarmDescription, Retry = 0 }; recordingData.GenerateStore(); return recordingData; } private async Task SendRecordingBroadcast(List recording) { if (!recording.IsNullOrEmpty()) { var patientNumber = recording[0].Patient?.Id; if (string.IsNullOrEmpty(patientNumber)) { _logger.LogError("Box not found by RoomId {roomId}", recording[0].RoomId); return; } var patient = await _patientService.Value.FindByPatientNumber(patientNumber); if (patient == null) return; var type = OperationType.RecordingStatus; var subscribers = _subscribersService.GetSubscribers().Where(s => s.SubscriptionType == SubscriptionEnum.WsType.Box && s.Box == patient.Location.Bed && s.Section == patient.Location.UnitName).ToList(); foreach (var subscriber in subscribers) await _clientMessageService.SendAsync(subscriber.Id, type, recording); } } protected static string Base64Encode(string plainText) { var plainTextBytes = Encoding.UTF8.GetBytes(plainText); return Convert.ToBase64String(plainTextBytes); } }