using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;

/// <summary>
/// Tests for gap-analysis fixes: MRN sequence, sepsis bundle idempotency,
/// trend alert exact match, order→bundle transaction, FHIR bundle rollback,
/// patient update endpoint, new list endpoints, and new validators.
/// </summary>
[Collection("Integration")]
public class GapAnalysisFixTests : IAsyncLifetime
{
    private readonly ApiFixture _fixture;
    private readonly HttpClient _client;

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

    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;

    // -------------------------------------------------------------------------
    // P0 — MRN generation uses sequence (no duplicates on concurrent registration)
    // -------------------------------------------------------------------------

    [Fact]
    public async Task MrnGeneration_SequentialRegistrations_ProduceUniqueMrns()
    {
        var resp1 = await _client.PostAsJsonAsync("/api/v1/patients",
            new RegisterPatientRequest("Alice", "One", new DateOnly(1990, 1, 1), "F"));
        var resp2 = await _client.PostAsJsonAsync("/api/v1/patients",
            new RegisterPatientRequest("Bob", "Two", new DateOnly(1991, 2, 2), "M"));

        resp1.StatusCode.Should().Be(HttpStatusCode.Created);
        resp2.StatusCode.Should().Be(HttpStatusCode.Created);

        var p1 = (await resp1.Content.ReadFromJsonAsync<JsonElement>())
            .GetProperty("data").GetProperty("mrn").GetString();
        var p2 = (await resp2.Content.ReadFromJsonAsync<JsonElement>())
            .GetProperty("data").GetProperty("mrn").GetString();

        p1.Should().StartWith("MRN-");
        p2.Should().StartWith("MRN-");
        p1.Should().NotBe(p2);
    }

    [Fact]
    public async Task MrnGeneration_FormatMatchesConfiguredPattern()
    {
        var resp = await _client.PostAsJsonAsync("/api/v1/patients",
            new RegisterPatientRequest("Format", "Test", new DateOnly(1985, 5, 5), "F"));
        resp.StatusCode.Should().Be(HttpStatusCode.Created);

        var mrn = (await resp.Content.ReadFromJsonAsync<JsonElement>())
            .GetProperty("data").GetProperty("mrn").GetString()!;

        mrn.Should().MatchRegex(@"^MRN-\d{6}$");
    }

    // -------------------------------------------------------------------------
    // P0 — Trend alert uses exact observation_code match
    // -------------------------------------------------------------------------

    [Fact]
    public async Task TrendAlert_StoresObservationCode()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var detector = scope.ServiceProvider.GetRequiredService<TrendDetector>();

        var patient = new Patient
        {
            Id = Guid.NewGuid(), Mrn = "MRN-TREND-OC", FirstName = "OC", LastName = "Test",
            DateOfBirth = new DateOnly(1970, 1, 1), Gender = "M",
            CreatedAt = DateTimeOffset.UtcNow
        };
        var encounter = new Encounter
        {
            Id = Guid.NewGuid(), PatientId = patient.Id, EncounterType = EncounterType.Inpatient,
            Status = EncounterStatus.Active, Department = Department.Icu,
            AttendingPhysician = "Dr. OC", AdmittedAt = DateTimeOffset.UtcNow,
            CreatedAt = DateTimeOffset.UtcNow
        };
        db.Patients.Add(patient);
        db.Encounters.Add(encounter);
        await db.SaveChangesAsync();

        var redis = scope.ServiceProvider.GetRequiredService<IConnectionMultiplexer>();
        foreach (var key in TrendCalculator.AllHistoryKeys(encounter.Id))
            await redis.GetDatabase().KeyDeleteAsync(key);

        var baseTime = new DateTimeOffset(2026, 6, 20, 10, 0, 0, TimeSpan.Zero);
        await detector.ProcessObservationAsync(
            encounter.Id, patient.Id, "HEART_RATE", 72m, baseTime);
        var result = await detector.ProcessObservationAsync(
            encounter.Id, patient.Id, "HEART_RATE", 95m, baseTime.AddMinutes(10));

        result.Outcome.Should().Be(TrendOutcome.RapidDeterioration);

        var alert = await db.ClinicalAlerts.SingleAsync(a =>
            a.EncounterId == encounter.Id && a.AlertType == AlertType.RapidDeterioration);
        alert.ObservationCode.Should().Be("HEART_RATE");
    }

    // -------------------------------------------------------------------------
    // P1 — Order→Bundle spanning transaction
    // -------------------------------------------------------------------------

    [Fact]
    public async Task OrderResult_UpdatesBundleElement_InSameTransaction()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var handler = scope.ServiceProvider.GetRequiredService<SepsisAlertHandler>();
        var bundleService = scope.ServiceProvider.GetRequiredService<ISepsisBundleService>();

        var patient = new Patient
        {
            Id = Guid.NewGuid(), Mrn = "MRN-TX-001", FirstName = "Tx", LastName = "Test",
            DateOfBirth = new DateOnly(1960, 1, 1), Gender = "M",
            CreatedAt = DateTimeOffset.UtcNow
        };
        var encounter = new Encounter
        {
            Id = Guid.NewGuid(), PatientId = patient.Id, EncounterType = EncounterType.Inpatient,
            Status = EncounterStatus.Active, Department = Department.Icu,
            AttendingPhysician = "Dr. Tx", AdmittedAt = DateTimeOffset.UtcNow,
            CreatedAt = DateTimeOffset.UtcNow
        };
        db.Patients.Add(patient);
        db.Encounters.Add(encounter);

        var alertId = Guid.NewGuid();
        db.ClinicalAlerts.Add(new ClinicalAlert
        {
            Id = alertId, EncounterId = encounter.Id, PatientId = patient.Id,
            AlertType = AlertType.SofaSepsis, Severity = AlertSeverity.Critical,
            Details = "SOFA delta +2", Status = AlertStatus.Open,
            TriggeredAt = DateTimeOffset.UtcNow
        });
        await db.SaveChangesAsync();

        await handler.OnSepsisAlertCreatedAsync(
            encounter.Id, alertId, AlertType.SofaSepsis, CancellationToken.None);

        var element = await db.SepsisBundleElements
            .Include(e => e.Order)
            .FirstAsync(e => e.ElementType == SepsisBundleElementType.SerumLactate);

        var orderService = scope.ServiceProvider.GetRequiredService<IOrderService>();
        var order = element.Order!;
        order.Status = OrderStatus.InProgress;
        await db.SaveChangesAsync();

        await orderService.RecordResultAsync(order.Id,
            new RecordOrderResultRequest("Lactate 1.5 mmol/L"));

        var updatedOrder = await db.Orders.AsNoTracking().FirstAsync(o => o.Id == order.Id);
        var updatedElement = await db.SepsisBundleElements.AsNoTracking()
            .FirstAsync(e => e.Id == element.Id);

        updatedOrder.Status.Should().Be(OrderStatus.Resulted);
        updatedElement.Status.Should().Be(SepsisBundleElementStatus.Completed);
    }

    // -------------------------------------------------------------------------
    // P4 — Patient update endpoint (PATCH)
    // -------------------------------------------------------------------------

    [Fact]
    public async Task PatientUpdate_PartialFields_UpdatesOnlyProvided()
    {
        var createResp = await _client.PostAsJsonAsync("/api/v1/patients",
            new RegisterPatientRequest("Original", "Name", new DateOnly(1990, 1, 1), "F"));
        createResp.StatusCode.Should().Be(HttpStatusCode.Created);
        var patientId = (await createResp.Content.ReadFromJsonAsync<JsonElement>())
            .GetProperty("data").GetProperty("id").GetGuid();

        var patchResp = await _client.PatchAsJsonAsync($"/api/v1/patients/{patientId}",
            new { lastName = "Updated", allergies = "Penicillin" });
        patchResp.StatusCode.Should().Be(HttpStatusCode.OK);

        var getResp = await _client.GetFromJsonAsync<JsonElement>($"/api/v1/patients/{patientId}");
        var data = getResp.GetProperty("data");
        data.GetProperty("firstName").GetString().Should().Be("Original");
        data.GetProperty("lastName").GetString().Should().Be("Updated");
        data.GetProperty("allergies").GetString().Should().Be("Penicillin");
    }

    [Fact]
    public async Task PatientUpdate_NonExistent_Returns404()
    {
        var resp = await _client.PatchAsJsonAsync($"/api/v1/patients/{Guid.NewGuid()}",
            new { firstName = "Ghost" });
        resp.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }

    [Fact]
    public async Task PatientUpdate_CreatesAuditLog()
    {
        var createResp = await _client.PostAsJsonAsync("/api/v1/patients",
            new RegisterPatientRequest("Audit", "Before", new DateOnly(1985, 3, 3), "M"));
        var patientId = (await createResp.Content.ReadFromJsonAsync<JsonElement>())
            .GetProperty("data").GetProperty("id").GetGuid();

        await _client.PatchAsJsonAsync($"/api/v1/patients/{patientId}",
            new { lastName = "After" });

        var auditResp = await _client.GetFromJsonAsync<JsonElement>(
            $"/api/v1/audit-logs?entityType=Patient&entityId={patientId}");
        var logs = auditResp.GetProperty("data").GetProperty("items");
        logs.EnumerateArray().Should().Contain(log =>
            log.GetProperty("action").GetString() == "PATIENT_UPDATED");
    }

    // -------------------------------------------------------------------------
    // P4 — Sepsis bundle list endpoint
    // -------------------------------------------------------------------------

    [Fact]
    public async Task SepsisBundleList_ReturnsPagedResults()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var handler = scope.ServiceProvider.GetRequiredService<SepsisAlertHandler>();

        var patient = new Patient
        {
            Id = Guid.NewGuid(), Mrn = "MRN-LIST-001", FirstName = "List", LastName = "Test",
            DateOfBirth = new DateOnly(1970, 1, 1), Gender = "F",
            CreatedAt = DateTimeOffset.UtcNow
        };
        var encounter = new Encounter
        {
            Id = Guid.NewGuid(), PatientId = patient.Id, EncounterType = EncounterType.Inpatient,
            Status = EncounterStatus.Active, Department = Department.Icu,
            AttendingPhysician = "Dr. List", AdmittedAt = DateTimeOffset.UtcNow,
            CreatedAt = DateTimeOffset.UtcNow
        };
        db.Patients.Add(patient);
        db.Encounters.Add(encounter);

        var alertId = Guid.NewGuid();
        db.ClinicalAlerts.Add(new ClinicalAlert
        {
            Id = alertId, EncounterId = encounter.Id, PatientId = patient.Id,
            AlertType = AlertType.SofaSepsis, Severity = AlertSeverity.Critical,
            Details = "SOFA delta +2", Status = AlertStatus.Open,
            TriggeredAt = DateTimeOffset.UtcNow
        });
        await db.SaveChangesAsync();

        await handler.OnSepsisAlertCreatedAsync(
            encounter.Id, alertId, AlertType.SofaSepsis, CancellationToken.None);

        var resp = await _client.GetFromJsonAsync<JsonElement>(
            "/api/v1/sepsis-bundles?status=IN_PROGRESS&page=1&pageSize=10");

        var data = resp.GetProperty("data");
        data.GetProperty("totalCount").GetInt32().Should().BeGreaterThanOrEqualTo(1);
        var bundle = data.GetProperty("items").EnumerateArray()
            .First(b => b.GetProperty("encounterId").GetGuid() == encounter.Id);
        bundle.GetProperty("complianceStatus").GetString().Should().Be("IN_PROGRESS");
        bundle.GetProperty("firstName").GetString().Should().Be("List");
        bundle.GetProperty("lastName").GetString().Should().Be("Test");
        bundle.GetProperty("mrn").GetString().Should().Be("MRN-LIST-001");
        bundle.GetProperty("department").GetString().Should().Be("ICU");
        bundle.GetProperty("elements").EnumerateArray().Should().HaveCount(4);
    }

    // -------------------------------------------------------------------------
    // P1 — encounter timeline endpoint
    // -------------------------------------------------------------------------

    [Fact]
    public async Task EncounterTimeline_ReturnsMergedEventTypes()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var patient = new Patient
        {
            Id = Guid.NewGuid(), Mrn = "MRN-TL-001", FirstName = "TL", LastName = "Test",
            DateOfBirth = new DateOnly(1980, 1, 1), Gender = "M",
            CreatedAt = DateTimeOffset.UtcNow
        };
        var admittedAt = DateTimeOffset.UtcNow.AddHours(-6);
        var encounter = new Encounter
        {
            Id = Guid.NewGuid(), PatientId = patient.Id, EncounterType = EncounterType.Inpatient,
            Status = EncounterStatus.Active, Department = Department.Icu,
            AttendingPhysician = "Dr. TL", AdmittedAt = admittedAt,
            CreatedAt = DateTimeOffset.UtcNow
        };
        db.Patients.Add(patient);
        db.Encounters.Add(encounter);

        db.Observations.Add(new Observation
        {
            Id = Guid.NewGuid(), EncounterId = encounter.Id,
            ObservationCode = "TEMP_C", Value = 38.8m, Unit = "C",
            Source = ObservationSource.Manual, RecordedAt = admittedAt.AddHours(1),
            CreatedAt = DateTimeOffset.UtcNow
        });
        db.ClinicalAlerts.Add(new ClinicalAlert
        {
            Id = Guid.NewGuid(), EncounterId = encounter.Id, PatientId = patient.Id,
            AlertType = AlertType.QsofaScreen, Severity = AlertSeverity.Warning,
            Details = "qSOFA screen", Status = AlertStatus.Open,
            TriggeredAt = admittedAt.AddHours(2)
        });
        db.Orders.Add(new Order
        {
            Id = Guid.NewGuid(), EncounterId = encounter.Id,
            OrderType = OrderType.Lab, Description = "Blood cultures",
            OrderedBy = "Dr. TL", Status = OrderStatus.Pending,
            OrderedAt = admittedAt.AddHours(3)
        });
        db.MedicationAdministrations.Add(new MedicationAdministration
        {
            Id = Guid.NewGuid(), EncounterId = encounter.Id,
            DrugName = "Ceftriaxone", Dose = 1m, DoseUnit = "g", Route = "IV",
            AdministeredAt = admittedAt.AddHours(4), AdministeredBy = "RN TL"
        });
        await db.SaveChangesAsync();

        var resp = await _client.GetFromJsonAsync<JsonElement>(
            $"/api/v1/encounters/{encounter.Id}/timeline");

        var items = resp.GetProperty("data").GetProperty("events");
        var types = items.EnumerateArray().Select(e => e.GetProperty("type").GetString()).ToList();
        types.Should().Contain("status");
        types.Should().Contain("observation");
        types.Should().Contain("alert");
        types.Should().Contain("order");
        types.Should().Contain("medication");
    }

    // -------------------------------------------------------------------------
    // P4 — qSOFA history endpoint
    // -------------------------------------------------------------------------

    [Fact]
    public async Task QsofaHistory_ReturnsEvaluationRecords()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var patient = new Patient
        {
            Id = Guid.NewGuid(), Mrn = "MRN-QH-001", FirstName = "QH", LastName = "Test",
            DateOfBirth = new DateOnly(1975, 1, 1), Gender = "F",
            CreatedAt = DateTimeOffset.UtcNow
        };
        var encounter = new Encounter
        {
            Id = Guid.NewGuid(), PatientId = patient.Id, EncounterType = EncounterType.Inpatient,
            Status = EncounterStatus.Active, Department = Department.Icu,
            AttendingPhysician = "Dr. QH", AdmittedAt = DateTimeOffset.UtcNow,
            CreatedAt = DateTimeOffset.UtcNow
        };
        db.Patients.Add(patient);
        db.Encounters.Add(encounter);

        var now = DateTimeOffset.UtcNow;
        db.QsofaEvaluations.Add(new QsofaEvaluation
        {
            Id = Guid.NewGuid(),
            EncounterId = encounter.Id,
            PatientId = patient.Id,
            ActiveCriteria = 1,
            RespRate = 24m,
            EvaluatedAt = now.AddMinutes(-30),
            CreatedAt = now,
        });
        db.QsofaEvaluations.Add(new QsofaEvaluation
        {
            Id = Guid.NewGuid(),
            EncounterId = encounter.Id,
            PatientId = patient.Id,
            ActiveCriteria = 2,
            RespRate = 24m,
            SystolicBp = 95m,
            ScreenAlertFired = true,
            EvaluatedAt = now.AddMinutes(-10),
            CreatedAt = now,
        });
        await db.SaveChangesAsync();

        var resp = await _client.GetFromJsonAsync<JsonElement>(
            $"/api/v1/encounters/{encounter.Id}/qsofa/history?limit=10");

        var data = resp.GetProperty("data");
        var items = data.GetProperty("items");
        items.GetArrayLength().Should().Be(2);

        var latest = items[0];
        latest.GetProperty("activeCriteria").GetInt32().Should().Be(2);
        latest.GetProperty("screenAlertFired").GetBoolean().Should().BeTrue();
    }

    // -------------------------------------------------------------------------
    // P4 — Reconciliation alerts endpoint
    // -------------------------------------------------------------------------

    [Fact]
    public async Task ReconciliationAlerts_ListWithFilter()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var patient = new Patient
        {
            Id = Guid.NewGuid(), Mrn = "MRN-RECON-001", FirstName = "Recon", LastName = "Test",
            DateOfBirth = new DateOnly(1980, 1, 1), Gender = "F",
            CreatedAt = DateTimeOffset.UtcNow
        };
        var encounter = new Encounter
        {
            Id = Guid.NewGuid(), PatientId = patient.Id, EncounterType = EncounterType.Inpatient,
            Status = EncounterStatus.Active, Department = Department.Icu,
            AttendingPhysician = "Dr. Recon", AdmittedAt = DateTimeOffset.UtcNow,
            CreatedAt = DateTimeOffset.UtcNow
        };
        db.Patients.Add(patient);
        db.Encounters.Add(encounter);
        db.ReconciliationAlerts.Add(new ReconciliationAlert
        {
            Id = Guid.NewGuid(),
            CheckType = ReconciliationCheckType.UnacknowledgedCriticalAlert,
            EncounterId = encounter.Id,
            PatientId = patient.Id,
            Details = "Test reconciliation alert",
            CreatedAt = DateTimeOffset.UtcNow
        });
        await db.SaveChangesAsync();

        var resp = await _client.GetFromJsonAsync<JsonElement>(
            "/api/v1/reconciliation-alerts?resolved=false&page=1&pageSize=10");

        var data = resp.GetProperty("data");
        data.GetProperty("totalCount").GetInt32().Should().BeGreaterThanOrEqualTo(1);
        data.GetProperty("items").EnumerateArray().Should().Contain(a =>
            a.GetProperty("details").GetString()!.Contains("Test reconciliation alert"));
    }

    [Fact]
    public async Task ReconciliationAlerts_FilterByCheckType()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var patient = new Patient
        {
            Id = Guid.NewGuid(), Mrn = "MRN-RECON-002", FirstName = "Recon2", LastName = "Test",
            DateOfBirth = new DateOnly(1980, 1, 1), Gender = "M",
            CreatedAt = DateTimeOffset.UtcNow
        };
        var encounter = new Encounter
        {
            Id = Guid.NewGuid(), PatientId = patient.Id, EncounterType = EncounterType.Inpatient,
            Status = EncounterStatus.Active, Department = Department.Icu,
            AttendingPhysician = "Dr. Recon2", AdmittedAt = DateTimeOffset.UtcNow,
            CreatedAt = DateTimeOffset.UtcNow
        };
        db.Patients.Add(patient);
        db.Encounters.Add(encounter);
        db.ReconciliationAlerts.Add(new ReconciliationAlert
        {
            Id = Guid.NewGuid(),
            CheckType = ReconciliationCheckType.PendingOrderNoResult,
            EncounterId = encounter.Id,
            Details = "Pending order check",
            CreatedAt = DateTimeOffset.UtcNow
        });
        await db.SaveChangesAsync();

        var resp = await _client.GetFromJsonAsync<JsonElement>(
            "/api/v1/reconciliation-alerts?checkType=PENDING_ORDER_NO_RESULT");
        var items = resp.GetProperty("data").GetProperty("items");
        items.GetArrayLength().Should().BeGreaterThanOrEqualTo(1);
    }

    // -------------------------------------------------------------------------
    // P2 — New validators return 400 for invalid input
    // -------------------------------------------------------------------------

    [Fact]
    public async Task TransitionStatusValidator_LongDiagnosis_Returns400()
    {
        var patientId = await CreatePatientAsync();
        var encounterId = await CreateEncounterAsync(patientId);

        var resp = await _client.PatchAsJsonAsync(
            $"/api/v1/encounters/{encounterId}/status",
            new { status = "Discharged", dischargeDiagnosis = new string('x', 501) });

        resp.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }

    [Fact]
    public async Task UpdatePatientValidator_LongFirstName_Returns400()
    {
        var patientId = await CreatePatientAsync();

        var resp = await _client.PatchAsJsonAsync(
            $"/api/v1/patients/{patientId}",
            new { firstName = new string('x', 101) });

        resp.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }

    // -------------------------------------------------------------------------
    // P2 — Outbox retry columns exist
    // -------------------------------------------------------------------------

    [Fact]
    public async Task OutboxEvent_HasRetryColumns()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var ev = new OutboxEvent
        {
            Id = Guid.NewGuid(),
            Topic = "test.retry",
            Payload = "{}",
            PartitionKey = "test",
            CreatedAt = DateTimeOffset.UtcNow,
            RetryCount = 3,
            LastError = "Connection refused",
            FailedAt = DateTimeOffset.UtcNow
        };
        db.OutboxEvents.Add(ev);
        await db.SaveChangesAsync();

        var loaded = await db.OutboxEvents.AsNoTracking().FirstAsync(e => e.Id == ev.Id);
        loaded.RetryCount.Should().Be(3);
        loaded.LastError.Should().Be("Connection refused");
        loaded.FailedAt.Should().NotBeNull();
    }

    // -------------------------------------------------------------------------
    // P0 — ClinicalAlert.ObservationCode column persists
    // -------------------------------------------------------------------------

    [Fact]
    public async Task ClinicalAlert_ObservationCode_Persists()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var patient = new Patient
        {
            Id = Guid.NewGuid(), Mrn = "MRN-OC-001", FirstName = "OC", LastName = "Persist",
            DateOfBirth = new DateOnly(1980, 1, 1), Gender = "F",
            CreatedAt = DateTimeOffset.UtcNow
        };
        var encounter = new Encounter
        {
            Id = Guid.NewGuid(), PatientId = patient.Id, EncounterType = EncounterType.Inpatient,
            Status = EncounterStatus.Active, Department = Department.Icu,
            AttendingPhysician = "Dr. OC", AdmittedAt = DateTimeOffset.UtcNow,
            CreatedAt = DateTimeOffset.UtcNow
        };
        db.Patients.Add(patient);
        db.Encounters.Add(encounter);

        var alert = new ClinicalAlert
        {
            Id = Guid.NewGuid(), EncounterId = encounter.Id, PatientId = patient.Id,
            AlertType = AlertType.RapidDeterioration, Severity = AlertSeverity.Warning,
            Details = "Test", ObservationCode = "SPO2", Status = AlertStatus.Open,
            TriggeredAt = DateTimeOffset.UtcNow
        };
        db.ClinicalAlerts.Add(alert);
        await db.SaveChangesAsync();

        var loaded = await db.ClinicalAlerts.AsNoTracking().FirstAsync(a => a.Id == alert.Id);
        loaded.ObservationCode.Should().Be("SPO2");
    }

    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------

    private async Task<Guid> CreatePatientAsync()
    {
        var resp = await _client.PostAsJsonAsync("/api/v1/patients",
            new RegisterPatientRequest("Gap", "Test", new DateOnly(1990, 1, 1), "F"));
        resp.EnsureSuccessStatusCode();
        var body = await resp.Content.ReadFromJsonAsync<JsonElement>();
        return body.GetProperty("data").GetProperty("id").GetGuid();
    }

    private async Task<Guid> CreateEncounterAsync(Guid patientId)
    {
        var resp = await _client.PostAsJsonAsync(
            $"/api/v1/patients/{patientId}/encounters",
            new OpenEncounterRequest(EncounterType.Inpatient, Department.Icu, "Dr. Gap"));
        resp.EnsureSuccessStatusCode();
        var body = await resp.Content.ReadFromJsonAsync<JsonElement>();
        return body.GetProperty("data").GetProperty("id").GetGuid();
    }
}
