using Hl7.Fhir.Model;
using Hl7.Fhir.Rest;
//using Pgo.Backend.Config;
//using System.Net;
//using System.Net.Http.Headers;

namespace Pgo.Backend.Clients;

public static class FhirClientServiceCollectionExtensions
{
    //public static string FhirHttpClientPgo = nameof(FhirHttpClientPgo);
    //public static string FhirHttpClientDva = nameof(FhirHttpClientDva);

    ///// <summary>
    ///// Register with services.AddFhirClientPgo() in Startup.cs. Simply inject with <see cref="IFhirClientPgo"/> in controller.
    ///// </summary>
    //public static IServiceCollection AddFhirClientPgo(this IServiceCollection services, PgoConfig config)
    //{
    //    // Configure client for JSON messages and avoid calling /metadata on every request.
    //    var settings = new FhirClientSettings
    //    {
    //        PreferredFormat = ResourceFormat.Json,
    //        VerifyFhirVersion = false,
    //        PreferredParameterHandling = SearchParameterHandling.Strict
    //    };

    //    // Configure messagehandler with defaults for FhirClient.
    //    services
    //        .AddHttpClient(FhirHttpClientPgo)
    //        .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()
    //        {
    //            AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
    //        });

    //    // Register the FhirClient as a transient service, but still with pooled socket handlers.
    //    services.AddTransient<IFhirClientPgo>(context =>
    //    {
    //        var httpClient = context
    //            .GetRequiredService<IHttpClientFactory>()
    //            .CreateClient(FhirHttpClientPgo);

    //        httpClient.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue
    //        {
    //            NoCache = true
    //        };

    //        // Wrap the client in a named service with an interface for dependency injection and tests.
    //        return new FhirClientPgo()
    //        {
    //            //BaseUri = new Uri(config.FhirEndpoint),
    //            Client = new FhirClient(config.FhirEndpoint, httpClient, settings)
    //        };
    //    });

    //    return services;
    //}

    /// <summary>
    /// Fetches all resources linked to a patient with the $everything operation. Optionally, fetches
    /// linked resources not yet contained in bundle. May want to use IResourceResolver for that.
    /// Note that Resource.contained is included in bundle responses by default. Linked resources are
    /// if _included or _revincluded in the query, optionally select for types, dates and ranges:
    /// $everything?_type=Encounter,CarePlan&_since=2025-01-01&_include=...&_revinclude=CarePlan:care-team
    /// https://pgo-koppelvlak-bron.t5t.nl/hapi/fhir/Patient/voorbeeldgebruiker/$everything?...
    /// </summary>
    public static async Task<List<Resource>> GetPatientResources(this FhirClient fhir, string patientId, bool resolveLinkedResources = false)
    {
        var result = new List<Resource>();

        // Query: include direct references from Patient
        var parameters = new Parameters
        {
            { "_include", new FhirString("Patient:organization") },
            { "_include", new FhirString("Patient:general-practitioner") },

            // Query: include resources where Patient is referenced
            { "_revinclude", new FhirString("Encounter:subject") },
            { "_revinclude", new FhirString("Observation:subject") },
            { "_revinclude", new FhirString("Condition:subject") },
            { "_revinclude", new FhirString("Procedure:subject") },
            { "_revinclude", new FhirString("MedicationRequest:subject") },
            { "_revinclude", new FhirString("AllergyIntolerance:patient") },
            { "_revinclude", new FhirString("CarePlan:subject") },
            { "_revinclude", new FhirString("DiagnosticReport:subject") },
            { "_revinclude", new FhirString("Immunization:patient") },

            // Query: recursive include references of referenced resources
            { "_include", new FhirString("Encounter:service-provider") },       // Patient <- Encounter   -> Organization
            { "_include", new FhirString("Encounter:participant.individual") }, // Patient <- Encounter   -> Practitioner
            { "_include", new FhirString("Observation:performer") },            // Patient <- Observation -> Practitioner
            { "_include", new FhirString("Condition:asserter") },               // Patient <- Condition   -> Practitioner
            { "_include", new FhirString("Procedure:performer.actor") },        // Patient <- Procedure   -> Practitioner
            { "_include", new FhirString("MedicationRequest:requester") },      // Patient <- Medication  -> Practitioner
            { "_include", new FhirString("CarePlan:care-team") }               // Patient <- CarePlan    -> CareTeam
        };

        try
        {
            // Result: returns a single resource (Patient), multiple entries (Bundle) or patient not found.
            var patient = await fhir.InstanceOperationAsync(new Uri($"Patient/{patientId}", UriKind.Relative), operationName: "everything", useGet: false, parameters: parameters);
            var bundle = patient as Bundle;

            // Collect: add resource entry to result when we receive a single resource (Patient).
            if (bundle == null && patient != null)
            {
                result.Add(patient);
            }

            // Collect: add entries when we receive multiple resources (Bundle) in paged results.
            while (bundle != null)
            {
                result.AddRange(bundle.Entry.Select(entry => entry.Resource));
                bundle = bundle.NextLink != null ? await fhir.ContinueAsync(bundle) : null;
            }
        }
        catch
        {
            // Result: Patient not found, returns empty list of resources.
            return result;
        }

        // Query: optionally add entries for any linked resources not yet included, e.g. Organisation, Practitioner.
        // These linked resources should be in bundle already if it was properly _included and found on server.
        if (resolveLinkedResources)
        {
            var linkedResources = new List<Resource>();
            foreach (var resource in result)
            {
                // Find reference fields like Patient.managingOrganization, DocumentManifest.content.reference
                // and EpisodeOfCare.careManager. Filter out unresolvable entries and entries already fetched.
                var linkedResourceReferencesFound = resource.GetType()
                    .GetProperties()
                    .Where(property => typeof(ResourceReference).IsAssignableFrom(property.PropertyType))
                    .Select(property => property.GetValue(resource) as ResourceReference)
                    .Where(reference => reference != null && !string.IsNullOrEmpty(reference?.Reference))
                    .Select(reference => reference!);

                // Fetch linked resources from server when not already in collected results. Most likely fails
                // in 404 if _include resource did not already return the resource earlier as part of bundle.
                // Simple way to resolve linked resources, an alternative to _include and IResourceResolver.
                // Useful for purpose of a quick demo, logging missing links, limited capabilities server, etc.
                foreach (var resourceReference in linkedResourceReferencesFound)
                {
                    bool isResourcePreviouslyResolved = result.Any(resource => $"{resource.TypeName}/{resource.Id}" == resourceReference.Reference) || linkedResources.Any(resource => $"{resource.TypeName}/{resource.Id}" == resourceReference.Reference);
                    if (!isResourcePreviouslyResolved)
                    {
                        try
                        {
                            var resolvedResource = await fhir.GetAsync(resourceReference.Reference);
                            if (resolvedResource != null)
                            {
                                linkedResources.Add(resolvedResource);
                            }
                        }
                        catch { /* Could not retrieve linked resource from server. */ }
                    }
                }
            }
            result.AddRange(linkedResources);
        }
        return result;
    }
}