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<RecordingService> _logger;
    private readonly Lazy<IPatientService> _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> rabbitMqSettings,
        IOptions<RecordingSettings> recordingSettings,
        ILogger<RecordingService> logger,
        IHttpClientFactory httpClientFactory,
        IPublisherService publisherService,
        IAuthService authService,
        IOptions<ApiSettings> apiSettings, IClientMessageService clientMessageService,
        ISubscribersService subscribersService,
        Lazy<IPatientService> 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<bool> 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<bool> SendAutoRecordingData(Patient patient, PointOfCare poc, RecordingData automaticRecording)
    {
        try
        {
            if (!Enum.TryParse<AlarmEnum.Name>(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<List<RecordingData>?> 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<List<RecordingData>>(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<RecordingData>();
            }
            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<Response<VideoDto>>(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<RecordingData> 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);
    }
}