using System.Net;
using System.Text;
using FluentAssertions;
using Hl7.Fhir.Model;
using Hl7.Fhir.Serialization;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;
using Task = System.Threading.Tasks.Task;

[Collection("Integration")]
public class FhirIngestTests : IAsyncLifetime
{
    private const string ApiKey = "dev-integration-key-change-in-production";

    private readonly ApiFixture _fixture;
    private readonly HttpClient _client;
    private static readonly FhirJsonSerializer Serializer = new();

    public FhirIngestTests(ApiFixture fixture)
    {
        _fixture = fixture;
        _client = fixture.CreateClient();
        _client.ClearAuth();
    }

    public async Task InitializeAsync()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        await DbResetHelper.ResetAsync(db);

        var redis = scope.ServiceProvider.GetRequiredService<IConnectionMultiplexer>();
        await DataSeeder.SeedThresholdsOnlyAsync(db, redis);
    }

    public Task DisposeAsync() => Task.CompletedTask;

    [Fact]
    public async Task PatientUpsert_IsIdempotent()
    {
        var patient = BuildPatient("MRN-HOSP-001");
        var json = Serializer.SerializeToString(patient);

        var resp1 = await PostFhirAsync("/fhir/R4/Patient", json);
        resp1.StatusCode.Should().Be(HttpStatusCode.Created);

        var resp2 = await PostFhirAsync("/fhir/R4/Patient", json);
        resp2.StatusCode.Should().Be(HttpStatusCode.Created);
    }

    [Fact]
    public async Task Observation_HeartRate_Loinc8867_MapsAndIngests()
    {
        var encounterId = await SeedPatientAndEncounterAsync();

        var obs = new Hl7.Fhir.Model.Observation
        {
            Status = ObservationStatus.Final,
            Category = new List<CodeableConcept>
            {
                new("http://terminology.hl7.org/CodeSystem/observation-category", "vital-signs")
            },
            Code = new CodeableConcept("http://loinc.org", "8867-4", "Heart rate"),
            Subject = new ResourceReference("Patient/test"),
            Encounter = new ResourceReference($"Encounter/{encounterId}"),
            Effective = new FhirDateTime(DateTimeOffset.UtcNow),
            Value = new Quantity(110, "/min", "http://unitsofmeasure.org"),
            Identifier = new List<Identifier>
            {
                new("http://hospital.example/obs", "hr-001")
            }
        };

        var resp = await PostFhirAsync("/fhir/R4/Observation", Serializer.SerializeToString(obs));
        resp.StatusCode.Should().Be(HttpStatusCode.Created);

        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var row = db.Observations.Single(o => o.IdempotencyKey == "hr-001");
        row.ObservationCode.Should().Be("HEART_RATE");
        row.Value.Should().Be(110m);
    }

    [Fact]
    public async Task Observation_UnknownLoinc_Returns422()
    {
        var encounterId = await SeedPatientAndEncounterAsync();
        var obs = new Hl7.Fhir.Model.Observation
        {
            Status = ObservationStatus.Final,
            Code = new CodeableConcept("http://loinc.org", "99999-9"),
            Encounter = new ResourceReference($"Encounter/{encounterId}"),
            Value = new Quantity(1, "1")
        };

        var resp = await PostFhirAsync("/fhir/R4/Observation", Serializer.SerializeToString(obs));
        resp.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
        (await resp.Content.ReadAsStringAsync()).Should().Contain("OperationOutcome");
    }

    [Fact]
    public async Task TransactionBundle_PatientEncounterObservation_Succeeds()
    {
        var bundle = BuildAdmitBundle();
        var resp = await PostFhirAsync("/fhir/R4", Serializer.SerializeToString(bundle));
        resp.StatusCode.Should().Be(HttpStatusCode.OK);

        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        db.Patients.Should().Contain(p => p.Mrn == "MRN-ADT-001");
        db.Encounters.Should().Contain(e => e.Status == EncounterStatus.Active);
        db.Observations.Should().Contain(o => o.ObservationCode == "HEART_RATE");
    }

    private async Task<HttpResponseMessage> PostFhirAsync(string path, string json)
    {
        var req = new HttpRequestMessage(HttpMethod.Post, path)
        {
            Content = new StringContent(json, Encoding.UTF8, "application/fhir+json")
        };
        req.Headers.Add("X-Api-Key", ApiKey);
        return await _client.SendAsync(req);
    }

    private static Hl7.Fhir.Model.Patient BuildPatient(string mrn) => new()
    {
        Identifier = new List<Identifier>
        {
            new("http://hospital.example/mrn", mrn)
            {
                Type = new CodeableConcept("http://terminology.hl7.org/CodeSystem/v2-0203", "MR")
            }
        },
        Name = new List<HumanName> { new() { Given = new[] { "Jane" }, Family = "Doe" } },
        BirthDate = "1980-01-15",
        Gender = AdministrativeGender.Female
    };

    private async Task<Guid> SeedPatientAndEncounterAsync()
    {
        using var scope = _fixture.Services.CreateScope();
        var patients = scope.ServiceProvider.GetRequiredService<IPatientService>();
        var patient = await patients.RegisterOrUpdateByIdentifierAsync(new FhirPatientUpsertRequest(
            "http://hospital.example/mrn", "MRN-TEST-001",
            "Test", "Patient", new DateOnly(1990, 1, 1), "female"));

        var encounters = scope.ServiceProvider.GetRequiredService<IEncounterService>();
        var encounter = await encounters.OpenOrUpdateByIdentifierAsync(new FhirEncounterUpsertRequest(
            "http://hospital.example/visit", "VISIT-001",
            patient.Id, EncounterType.Inpatient, Department.Icu,
            "Dr. Smith", EncounterStatus.Active));

        return encounter.Id;
    }

    private static Bundle BuildAdmitBundle()
    {
        var patient = BuildPatient("MRN-ADT-001");
        var encounter = new Hl7.Fhir.Model.Encounter
        {
            Status = Hl7.Fhir.Model.Encounter.EncounterStatus.InProgress,
            Class = new Coding("http://terminology.hl7.org/CodeSystem/v3-ActCode", "IMP"),
            Identifier = new List<Identifier> { new("http://hospital.example/visit", "VISIT-ADT-001") },
            Subject = new ResourceReference
            {
                Identifier = new Identifier("http://hospital.example/mrn", "MRN-ADT-001")
            },
            Participant = new List<Hl7.Fhir.Model.Encounter.ParticipantComponent>
            {
                new()
                {
                    Type = new List<CodeableConcept>
                    {
                        new("http://terminology.hl7.org/CodeSystem/v3-ParticipationType", "ATND")
                    },
                    Individual = new ResourceReference { Display = "Dr. Admit" }
                }
            }
        };
        var obs = new Hl7.Fhir.Model.Observation
        {
            Status = ObservationStatus.Final,
            Code = new CodeableConcept("http://loinc.org", "8867-4"),
            Encounter = new ResourceReference
            {
                Identifier = new Identifier("http://hospital.example/visit", "VISIT-ADT-001")
            },
            Value = new Quantity(88, "/min"),
            Identifier = new List<Identifier> { new("http://hospital.example/obs", "adt-hr-1") }
        };

        return new Bundle
        {
            Type = Bundle.BundleType.Transaction,
            Entry = new List<Bundle.EntryComponent>
            {
                new() { Resource = patient, Request = new Bundle.RequestComponent { Method = Bundle.HTTPVerb.POST, Url = "Patient" } },
                new() { Resource = encounter, Request = new Bundle.RequestComponent { Method = Bundle.HTTPVerb.POST, Url = "Encounter" } },
                new() { Resource = obs, Request = new Bundle.RequestComponent { Method = Bundle.HTTPVerb.POST, Url = "Observation" } }
            }
        };
    }
}
