using adas_core.Domain.Models;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Providers;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Serilog;

namespace adas_core.Application.Providers;

public class AdasProvider(IOptions<ProvidersSettings> providerSettings, IHttpClientFactory httpClientFactory)
    : BaseProvider(providerSettings, httpClientFactory)
{
    /*
     * Get observations of:
     * Predict of UCI stay duration
     * Predict of medications for patients
     */
    /// <summary>
    /// Retrieves patient observations from the ADAS system, including stay duration predictions (most and less confident scenarios) and medication predictions.
    /// Logs an error and skips stay prediction observations if the prediction result is null or contains an error.
    /// Skips the medication prediction observation if no medication predictions are returned.
    /// </summary>
    /// <param name="patient">The patient for whom observations are being calculated.</param>
    /// <returns>A task that represents the asynchronous operation, returning a list of patient observations.</returns>
    public override async Task<List<PatientObservation>> GetObservations(Patient patient)
    {
        var calculatedObservations = new List<PatientObservation>();

        //get data from two endpoints one from UCI stay another for medication
        var predictOfStay = await GetPredictOfStay(patient.PatientNumber);

        if (predictOfStay == null || predictOfStay.Result.ToLower().Contains("error"))
            Log.Error("Error retrieving predict of stay in ADAS result, message: {predictOfStay}", predictOfStay);
        else
        {
            var durationPredicted1 = predictOfStay.Payload.MaxBy(i => i.PercentageMin)!.Duration;
            var durationPredicted2 = predictOfStay.Payload.MinBy(i => i.PercentageMin)!.Duration;

            calculatedObservations.Add(
                new PatientObservation
                {
                    PatientId = patient.Id,
                    CodingSystem = "ADAS",
                    Name = "Stay_Predict_Days_Most_Confident",
                    //get the most confident interval to show the most probable scenario
                    Value = durationPredicted1,
                    Time = DateTime.UtcNow
                });

            calculatedObservations.Add(new PatientObservation
            {
                PatientId = patient.Id,
                CodingSystem = "ADAS",
                Name = "Stay_Predict_Days_Less_Confident",
                //get the less confident interval to show the less probable scenario
                Value = durationPredicted2,
                Time = DateTime.UtcNow
            });
        }


        var predictMedication = await GetPredictMedications(patient.PatientNumber);


        if (predictMedication != null && predictMedication.Any())
            calculatedObservations.Add(new PatientObservation
            {
                PatientId = patient.Id,
                CodingSystem = "ADAS",
                Name = "Medication_Predict",
                Value = ParseAdasMedicationsToMedicationObservation(predictMedication),
                Time = DateTime.UtcNow
            });


        return calculatedObservations;
    }

    /// <summary>
    /// Concatenates up to the first five ADAS medication prediction results into a single caret-delimited string for use as a medication observation value.
    /// </summary>
    /// <param name="medicationPredict">The list of ADAS medication prediction results to be serialized. Only the first five entries are included.</param>
    /// <returns>A string containing the selected medication prediction entries joined by the "^" delimiter.</returns>
    private static string ParseAdasMedicationsToMedicationObservation(
            List<ResultModelPredictMedicationAdas> medicationPredict)
    {
        return string.Join("^", medicationPredict.Take(5));
    }

    /// <summary>
    /// Retrieves the ADAS prediction of length of stay for the specified patient from an external service.
    /// Returns null if the patient number is null, if the service response is unsuccessful, or if an exception occurs during the request.
    /// </summary>
    /// <param name="patientNumber">The unique identifier of the patient whose ADAS stay prediction is being requested.</param>
    /// <returns>A task containing the deserialized <see cref="ResultModelPredictOfStayAdas"/> result on success, or null if the patient number is null, the response is unsuccessful, or the call throws.</returns>
    private async Task<ResultModelPredictOfStayAdas?> GetPredictOfStay(string? patientNumber)
    {
        if (patientNumber == null)
        {
            Log.Error("Error retrieving ADAS predict of stay patient number is null");
            return null;
        }

        try
        {
            var client = GetClient();
            var response = await client.GetAsync($"predictStay?patientNumber={patientNumber}");

            if (response.IsSuccessStatusCode)
            {
                var content = await response.Content.ReadAsStringAsync();
                return JsonConvert.DeserializeObject<ResultModelPredictOfStayAdas>(content);
            }

            Log.Error(
                "Error retrieving calculated ADAS observation length of stay for patient: {patientNumber}, status code: {response.StatusCode} ",
                patientNumber, response.StatusCode);
            return null;
        }


        catch (Exception e)
        {
            Log.Error("Exception retrieving ADAS predict of stay for patient: {patientNumber} exception: {e}",
                patientNumber, e.Message);
            return null;
        }
    }

    /// <summary>
    /// Retrieves the predicted medication needs for a given patient by calling the pharmacy prediction service.
    /// Returns null if the patient number is null, the service response is unsuccessful, or an exception occurs during the request.
    /// </summary>
    /// <param name="patientNumber">The unique identifier of the patient whose ADAS medication predictions are being retrieved.</param>
    /// <returns>A task containing a list of <see cref="ResultModelPredictMedicationAdas"/> with the predicted medication data, or null if the request fails or the patient number is null.</returns>
    private async Task<List<ResultModelPredictMedicationAdas>?> GetPredictMedications(string? patientNumber)
    {
        if (patientNumber == null)
        {
            Log.Error("Error retrieving ADAS predict of stay for patient number is null");
            return null;
        }

        try
        {
            var client = GetClient();

            var response = await client.GetAsync($"predictMedicationNeedsPharmacy?patientNumber={patientNumber}");
            if (response.IsSuccessStatusCode)
            {
                var content = await response.Content.ReadAsStringAsync();
                return JsonConvert.DeserializeObject<List<ResultModelPredictMedicationAdas>>(content);
            }

            Log.Error(
                "Error retrieving calculated ADAS medicines for patient: {patientNumber}, status code: {response.StatusCode}",
                patientNumber, response.StatusCode);
            return null;
        }
        catch (Exception e)
        {
            Log.Error("Exception retrieving ADAS predict medicines for patient: {patientNumber} exception: {e}",
                patientNumber, e.Message);
            return null;
        }
    }

    /// <summary>
    /// Creates a new <see cref="HttpClient"/> instance and configures it with the configured base address URL.
    /// </summary>
    /// <returns>An <see cref="HttpClient"/> with its <see cref="HttpClient.BaseAddress"/> set to the configured <c>Url</c>.</returns>
    private HttpClient GetClient()
    {
        var client = HttpClientFactory.CreateClient();
        client.BaseAddress = new Uri(Url);
        return client;
    }
}