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

[Collection("Database")]
public class LiveCaptureIntegrationTests : IAsyncLifetime
{
    private readonly ApiFixture _fixture;
    private HttpClient _client = null!;

    public LiveCaptureIntegrationTests(ApiFixture fixture) => _fixture = fixture;

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

    public Task DisposeAsync() => Task.CompletedTask;

    // ───────────────────────────────────────────────
    // Happy path: normal observations, no alert
    // ───────────────────────────────────────────────

    [Fact]
    public async Task RecordObservations_NormalValues_PromotesSynchronouslyWithNoAlert()
    {
        // Arrange — create patient and encounter via setup
        _client = await AuthHelper.LoginAsync(_fixture, "clinician1");

        var (patientId, encounterId) = await CreateTestPatientAndEncounterAsync();

        var request = new RecordObservationsRequest(
            Observations: new List<LiveCaptureObservationRequest>
            {
                new("HEART_RATE", 72m, "bpm", DateTimeOffset.UtcNow, null),
                new("TEMP_C", 36.8m, "C", DateTimeOffset.UtcNow, null),
                new("SPO2", 98m, "%", DateTimeOffset.UtcNow, null)
            },
            ClinicianAttestation: true,
            PasswordConfirm: "password"
        );

        // Act
        var response = await _client.PostAsJsonAsync(
            $"/api/v1/live-capture/encounters/{encounterId}/observations", request);

        // Assert
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);

        var body = await response.Content
            .ReadFromJsonAsync<ApiResponse<LiveCaptureResponse>>();
        Assert.True(body!.Success);
        Assert.Equal(encounterId, body.Data!.EncounterId);
        Assert.Equal(3, body.Data.Observations.Count);
        Assert.Equal(0, body.Data.CriticalAlertCount);

        // Verify all observations have live IDs
        foreach (var obs in body.Data.Observations)
        {
            Assert.NotEqual(Guid.Empty, obs.LiveObservationId);
            Assert.NotEqual(Guid.Empty, obs.DraftObservationId);
            Assert.Null(obs.CriticalAlert);
        }

        // Verify batch exists in database with Promoted status
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var batch = await db.DigitizationBatches.FindAsync(body.Data.BatchId);
        Assert.NotNull(batch);
        Assert.Equal(BatchStatus.Promoted, batch!.Status);
        Assert.Equal(BatchTrack.LiveCapture, batch.Track);
        Assert.True(batch.ClinicianAttestation);
        Assert.NotNull(batch.PromotedAt);
    }

    // ───────────────────────────────────────────────
    // Critical path: alert fires BEFORE response returns
    // ───────────────────────────────────────────────

    [Fact]
    public async Task RecordObservations_CriticalPotassium_AlertFiresBeforeResponseReturns()
    {
        // Arrange
        _client = await AuthHelper.LoginAsync(_fixture, "clinician1");

        var (patientId, encounterId) = await CreateTestPatientAndEncounterAsync();

        // Seed a critical threshold for potassium:
        // critical_low = 2.5, warning_low = 3.5, warning_high = 5.0, critical_high = 6.5
        await SeedPotassiumThresholdAsync();

        // Value of 2.1 is below critical_low of 2.5 — life-threatening
        var request = new RecordObservationsRequest(
            Observations: new List<LiveCaptureObservationRequest>
            {
                new("POTASSIUM_MEQ_L", 2.1m, "mEq/L", DateTimeOffset.UtcNow,
                    "Bedside iSTAT result")
            },
            ClinicianAttestation: true,
            PasswordConfirm: "password"
        );

        // Act
        var response = await _client.PostAsJsonAsync(
            $"/api/v1/live-capture/encounters/{encounterId}/observations", request);

        // Assert — response contains the alert inline
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);

        var body = await response.Content
            .ReadFromJsonAsync<ApiResponse<LiveCaptureResponse>>();
        Assert.True(body!.Success);
        Assert.Equal(1, body.Data!.CriticalAlertCount);
        Assert.Single(body.Data.Observations);

        var obsResponse = body.Data.Observations[0];
        Assert.NotNull(obsResponse.CriticalAlert);
        Assert.Equal("CRITICAL", obsResponse.CriticalAlert!.Severity);
        Assert.Equal("CRITICAL_LOW", obsResponse.CriticalAlert.ThresholdBound);
        Assert.Equal(2.5m, obsResponse.CriticalAlert.ThresholdValue);
        Assert.Contains("below critical low", obsResponse.CriticalAlert.Message);

        // Verify the alert exists in the database BEFORE we read anything else —
        // the alert was committed in the same transaction as the observation
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var alert = await db.ClinicalAlerts.FindAsync(obsResponse.CriticalAlert.AlertId);
        Assert.NotNull(alert);
        Assert.Equal(AlertStatus.Open, alert!.Status);
        Assert.Equal(AlertSeverity.Critical, alert.Severity);
        Assert.Equal(AlertType.CriticalPotassiumMeqL, alert.AlertType);
        Assert.Contains("below critical low", alert.Details);

        // Verify outbox event for alert was written in the same transaction
        var alertOutbox = await db.OutboxEvents
            .Where(e => e.EventType == "alert.generated" &&
                        e.AggregateId == obsResponse.CriticalAlert.AlertId)
            .FirstOrDefaultAsync();
        Assert.NotNull(alertOutbox);
        Assert.Contains(obsResponse.CriticalAlert.AlertId.ToString(), alertOutbox!.PayloadJson);
    }

    // ───────────────────────────────────────────────
    // Critical high: value above critical_high threshold
    // ───────────────────────────────────────────────

    [Fact]
    public async Task RecordObservations_CriticalHighPotassium_AlertWithCorrectBound()
    {
        // Arrange
        _client = await AuthHelper.LoginAsync(_fixture, "clinician1");

        var (_, encounterId) = await CreateTestPatientAndEncounterAsync();
        await SeedPotassiumThresholdAsync();

        // Value of 7.2 is above critical_high of 6.5
        var request = new RecordObservationsRequest(
            Observations: new List<LiveCaptureObservationRequest>
            {
                new("POTASSIUM_MEQ_L", 7.2m, "mEq/L", DateTimeOffset.UtcNow, null)
            },
            ClinicianAttestation: true,
            PasswordConfirm: "password"
        );

        // Act
        var response = await _client.PostAsJsonAsync(
            $"/api/v1/live-capture/encounters/{encounterId}/observations", request);

        // Assert
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
        var body = await response.Content
            .ReadFromJsonAsync<ApiResponse<LiveCaptureResponse>>();

        var alert = body!.Data!.Observations[0].CriticalAlert;
        Assert.NotNull(alert);
        Assert.Equal("CRITICAL_HIGH", alert!.ThresholdBound);
        Assert.Equal(6.5m, alert.ThresholdValue);
        Assert.Contains("above critical high", alert.Message);
    }

    // ───────────────────────────────────────────────
    // Mixed batch: one critical, two normal
    // ───────────────────────────────────────────────

    [Fact]
    public async Task RecordObservations_MixedBatch_OnlyCriticalObservationGetsAlert()
    {
        _client = await AuthHelper.LoginAsync(_fixture, "clinician1");

        var (_, encounterId) = await CreateTestPatientAndEncounterAsync();
        await SeedPotassiumThresholdAsync();

        var request = new RecordObservationsRequest(
            Observations: new List<LiveCaptureObservationRequest>
            {
                new("HEART_RATE", 80m, "bpm", DateTimeOffset.UtcNow, null),
                new("POTASSIUM_MEQ_L", 2.1m, "mEq/L", DateTimeOffset.UtcNow, null),
                new("TEMP_C", 37.0m, "C", DateTimeOffset.UtcNow, null)
            },
            ClinicianAttestation: true,
            PasswordConfirm: "password"
        );

        var response = await _client.PostAsJsonAsync(
            $"/api/v1/live-capture/encounters/{encounterId}/observations", request);

        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
        var body = await response.Content
            .ReadFromJsonAsync<ApiResponse<LiveCaptureResponse>>();

        Assert.Equal(1, body!.Data!.CriticalAlertCount);
        Assert.Null(body.Data.Observations[0].CriticalAlert); // heart rate — normal
        Assert.NotNull(body.Data.Observations[1].CriticalAlert); // potassium — critical
        Assert.Null(body.Data.Observations[2].CriticalAlert); // temp — normal
    }

    // ───────────────────────────────────────────────
    // Open encounter + vitals in one request
    // ───────────────────────────────────────────────

    [Fact]
    public async Task OpenEncounterWithVitals_CreatesEncounterAndPromotesObservations()
    {
        _client = await AuthHelper.LoginAsync(_fixture, "clinician1");

        var patientId = await CreateTestPatientAsync();

        var request = new OpenEncounterWithVitalsRequest(
            PatientId: patientId,
            Department: "Outpatient Clinic",
            RoomBed: "OPD-3",
            AdmissionReason: "Follow-up consultation",
            Observations: new List<LiveCaptureObservationRequest>
            {
                new("HEART_RATE", 68m, "bpm", DateTimeOffset.UtcNow, null),
                new("BP_SYSTOLIC", 120m, "mmHg", DateTimeOffset.UtcNow, null),
                new("BP_DIASTOLIC", 80m, "mmHg", DateTimeOffset.UtcNow, null)
            },
            ClinicianAttestation: true,
            PasswordConfirm: "password"
        );

        var response = await _client.PostAsJsonAsync(
            "/api/v1/live-capture/encounters", request);

        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
        var body = await response.Content
            .ReadFromJsonAsync<ApiResponse<LiveCaptureResponse>>();

        Assert.True(body!.Success);
        Assert.NotEqual(Guid.Empty, body.Data!.EncounterId);
        Assert.Equal(3, body.Data.Observations.Count);
        Assert.Equal(0, body.Data.CriticalAlertCount);

        // Verify encounter was created in VigilCareClinical
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var encounter = await db.Encounters.FindAsync(body.Data.EncounterId);
        Assert.NotNull(encounter);
        Assert.Equal("active", encounter!.Status);
        Assert.Equal(Department.OutpatientClinic, encounter.Department);
    }

    // ───────────────────────────────────────────────
    // Authorization: non-clinician role rejected
    // ───────────────────────────────────────────────

    [Fact]
    public async Task RecordObservations_NonClinicianRole_Returns403()
    {
        _client = await AuthHelper.LoginAsync(_fixture, "entry1");

        var request = new RecordObservationsRequest(
            Observations: new List<LiveCaptureObservationRequest>
            {
                new("HEART_RATE", 72m, "bpm", DateTimeOffset.UtcNow, null)
            },
            ClinicianAttestation: true,
            PasswordConfirm: "password"
        );

        var response = await _client.PostAsJsonAsync(
            $"/api/v1/live-capture/encounters/{Guid.NewGuid()}/observations", request);

        Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
    }

    // ───────────────────────────────────────────────
    // Attestation: false attestation rejected
    // ───────────────────────────────────────────────

    [Fact]
    public async Task RecordObservations_AttestationFalse_Returns422()
    {
        _client = await AuthHelper.LoginAsync(_fixture, "clinician1");

        var (_, encounterId) = await CreateTestPatientAndEncounterAsync();

        var request = new RecordObservationsRequest(
            Observations: new List<LiveCaptureObservationRequest>
            {
                new("HEART_RATE", 72m, "bpm", DateTimeOffset.UtcNow, null)
            },
            ClinicianAttestation: false, // Must be true
            PasswordConfirm: "password"
        );

        var response = await _client.PostAsJsonAsync(
            $"/api/v1/live-capture/encounters/{encounterId}/observations", request);

        Assert.Equal(HttpStatusCode.UnprocessableEntity, response.StatusCode);
        var body = await response.Content
            .ReadFromJsonAsync<ApiResponse<object>>();
        Assert.Equal("ATTESTATION_REQUIRED", body!.Error!.Code);
    }

    // ───────────────────────────────────────────────
    // Attestation: wrong password rejected
    // ───────────────────────────────────────────────

    [Fact]
    public async Task RecordObservations_WrongPassword_Returns422()
    {
        _client = await AuthHelper.LoginAsync(_fixture, "clinician1");

        var (_, encounterId) = await CreateTestPatientAndEncounterAsync();

        var request = new RecordObservationsRequest(
            Observations: new List<LiveCaptureObservationRequest>
            {
                new("HEART_RATE", 72m, "bpm", DateTimeOffset.UtcNow, null)
            },
            ClinicianAttestation: true,
            PasswordConfirm: "wrong-password" // Incorrect
        );

        var response = await _client.PostAsJsonAsync(
            $"/api/v1/live-capture/encounters/{encounterId}/observations", request);

        Assert.Equal(HttpStatusCode.UnprocessableEntity, response.StatusCode);
        var body = await response.Content
            .ReadFromJsonAsync<ApiResponse<object>>();
        Assert.Equal("PASSWORD_CONFIRM_INVALID", body!.Error!.Code);
    }

    // ───────────────────────────────────────────────
    // Edge case: discharged encounter rejected
    // ───────────────────────────────────────────────

    [Fact]
    public async Task RecordObservations_DischargedEncounter_Returns409()
    {
        _client = await AuthHelper.LoginAsync(_fixture, "clinician1");

        var (_, encounterId) = await CreateTestPatientAndDischargedEncounterAsync();

        var request = new RecordObservationsRequest(
            Observations: new List<LiveCaptureObservationRequest>
            {
                new("HEART_RATE", 72m, "bpm", DateTimeOffset.UtcNow, null)
            },
            ClinicianAttestation: true,
            PasswordConfirm: "password"
        );

        var response = await _client.PostAsJsonAsync(
            $"/api/v1/live-capture/encounters/{encounterId}/observations", request);

        Assert.Equal(HttpStatusCode.Conflict, response.StatusCode);
        var body = await response.Content
            .ReadFromJsonAsync<ApiResponse<object>>();
        Assert.Equal("ENCOUNTER_NOT_ACTIVE", body!.Error!.Code);
    }

    // ───────────────────────────────────────────────
    // Edge case: duplicate active encounter
    // ───────────────────────────────────────────────

    [Fact]
    public async Task OpenEncounterWithVitals_PatientHasActiveEncounter_Returns409()
    {
        _client = await AuthHelper.LoginAsync(_fixture, "clinician1");

        var (patientId, _) = await CreateTestPatientAndEncounterAsync();

        var request = new OpenEncounterWithVitalsRequest(
            PatientId: patientId,
            Department: Department.EmergencyDepartment.ToDbString(),
            RoomBed: "ER-1",
            AdmissionReason: "Chest pain",
            Observations: new List<LiveCaptureObservationRequest>
            {
                new("HEART_RATE", 90m, "bpm", DateTimeOffset.UtcNow, null)
            },
            ClinicianAttestation: true,
            PasswordConfirm: "password"
        );

        var response = await _client.PostAsJsonAsync(
            "/api/v1/live-capture/encounters", request);

        Assert.Equal(HttpStatusCode.Conflict, response.StatusCode);
        var body = await response.Content
            .ReadFromJsonAsync<ApiResponse<object>>();
        Assert.Equal("ACTIVE_ENCOUNTER_EXISTS", body!.Error!.Code);
    }

    // ───────────────────────────────────────────────
    // Edge case: empty observations rejected
    // ───────────────────────────────────────────────

    [Fact]
    public async Task RecordObservations_EmptyList_Returns422()
    {
        _client = await AuthHelper.LoginAsync(_fixture, "clinician1");

        var (_, encounterId) = await CreateTestPatientAndEncounterAsync();

        var request = new RecordObservationsRequest(
            Observations: new List<LiveCaptureObservationRequest>(),
            ClinicianAttestation: true,
            PasswordConfirm: "password"
        );

        var response = await _client.PostAsJsonAsync(
            $"/api/v1/live-capture/encounters/{encounterId}/observations", request);

        Assert.Equal(HttpStatusCode.UnprocessableEntity, response.StatusCode);
        var body = await response.Content
            .ReadFromJsonAsync<ApiResponse<object>>();
        Assert.Equal("EMPTY_OBSERVATIONS", body!.Error!.Code);
    }

    // ───────────────────────────────────────────────
    // Audit trail: verify digitization events written
    // ───────────────────────────────────────────────

    [Fact]
    public async Task RecordObservations_WritesAttestationAndPromotionEvents()
    {
        _client = await AuthHelper.LoginAsync(_fixture, "clinician1");

        var (_, encounterId) = await CreateTestPatientAndEncounterAsync();

        var request = new RecordObservationsRequest(
            Observations: new List<LiveCaptureObservationRequest>
            {
                new("HEART_RATE", 72m, "bpm", DateTimeOffset.UtcNow, null)
            },
            ClinicianAttestation: true,
            PasswordConfirm: "password"
        );

        var response = await _client.PostAsJsonAsync(
            $"/api/v1/live-capture/encounters/{encounterId}/observations", request);

        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
        var body = await response.Content
            .ReadFromJsonAsync<ApiResponse<LiveCaptureResponse>>();

        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var events = await db.DigitizationEvents
            .Where(e => e.BatchId == body!.Data!.BatchId)
            .OrderBy(e => e.OccurredAt)
            .ToListAsync();

        Assert.Equal(2, events.Count);
        Assert.Equal(DigitizationEventType.LiveCaptureAttested, events[0].EventType);
        Assert.Equal(DigitizationEventType.Promoted, events[1].EventType);

        // Verify metadata contains clinician info
        Assert.Contains("clinicianName", events[0].MetadataJson!);
        Assert.Contains("LIVE_CAPTURE", events[0].MetadataJson!);
    }

    // ───────────────────────────────────────────────
    // Helpers
    // ───────────────────────────────────────────────

    private async Task<Guid> CreateTestPatientAsync()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var patient = new Patient
        {
            Id = Guid.NewGuid(),
            Mrn = $"MRN-{Guid.NewGuid():N}"[..12],
            FullName = "Test Patient",
            DateOfBirth = new DateOnly(1985, 3, 15),
            Sex = "M",
            BloodType = BloodType.OPos,
            CreatedAt = DateTimeOffset.UtcNow,
            UpdatedAt = DateTimeOffset.UtcNow
        };

        db.Patients.Add(patient);
        await db.SaveChangesAsync();
        return patient.Id;
    }

    private async Task<(Guid patientId, Guid encounterId)> CreateTestPatientAndEncounterAsync()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var patient = new Patient
        {
            Id = Guid.NewGuid(),
            Mrn = $"MRN-{Guid.NewGuid():N}"[..12],
            FullName = "Test Patient",
            DateOfBirth = new DateOnly(1985, 3, 15),
            Sex = "M",
            BloodType = BloodType.OPos,
            CreatedAt = DateTimeOffset.UtcNow,
            UpdatedAt = DateTimeOffset.UtcNow
        };

        var encounter = new Encounter
        {
            Id = Guid.NewGuid(),
            PatientId = patient.Id,
            Department = Department.InternalMedicine,
            RoomBed = "IM-201A",
            AdmissionReason = "Observation",
            Status = "active",
            AdmissionDate = DateTimeOffset.UtcNow,
            CreatedAt = DateTimeOffset.UtcNow,
            UpdatedAt = DateTimeOffset.UtcNow
        };

        db.Patients.Add(patient);
        db.Encounters.Add(encounter);
        await db.SaveChangesAsync();

        return (patient.Id, encounter.Id);
    }

    private async Task<(Guid patientId, Guid encounterId)> CreateTestPatientAndDischargedEncounterAsync()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var patient = new Patient
        {
            Id = Guid.NewGuid(),
            Mrn = $"MRN-{Guid.NewGuid():N}"[..12],
            FullName = "Discharged Patient",
            DateOfBirth = new DateOnly(1970, 7, 20),
            Sex = "F",
            BloodType = BloodType.ANeg,
            CreatedAt = DateTimeOffset.UtcNow,
            UpdatedAt = DateTimeOffset.UtcNow
        };

        var encounter = new Encounter
        {
            Id = Guid.NewGuid(),
            PatientId = patient.Id,
            Department = Department.Surgery,
            RoomBed = "SURG-105",
            AdmissionReason = "Post-op recovery",
            Status = "discharged",
            AdmissionDate = DateTimeOffset.UtcNow.AddDays(-3),
            CreatedAt = DateTimeOffset.UtcNow.AddDays(-3),
            UpdatedAt = DateTimeOffset.UtcNow
        };

        db.Patients.Add(patient);
        db.Encounters.Add(encounter);
        await db.SaveChangesAsync();

        return (patient.Id, encounter.Id);
    }

    private async Task SeedPotassiumThresholdAsync()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var redis = scope.ServiceProvider.GetRequiredService<IConnectionMultiplexer>();

        // Only seed if not already present
        var existing = await db.AlertThresholds
            .FirstOrDefaultAsync(t => t.ObservationCode == "POTASSIUM_MEQ_L");

        if (existing is null)
        {
            var threshold = new AlertThreshold
            {
                Id = Guid.NewGuid(),
                ObservationCode = "POTASSIUM_MEQ_L",
                DisplayName = "Serum Potassium",
                Unit = "mEq/L",
                CriticalLow = 2.5m,
                WarningLow = 3.5m,
                WarningHigh = 5.0m,
                CriticalHigh = 6.5m,
                CreatedAt = DateTimeOffset.UtcNow
            };

            db.AlertThresholds.Add(threshold);
            await db.SaveChangesAsync();

            // Pre-warm Redis cache with ThresholdCacheEntry (same shape as LiveCaptureService)
            var cache = redis.GetDatabase();
            await cache.StringSetAsync(
                "threshold:POTASSIUM_MEQ_L",
                JsonSerializer.Serialize(new ThresholdCacheEntry(
                    threshold.ObservationCode,
                    threshold.CriticalLow,
                    threshold.WarningLow,
                    threshold.WarningHigh,
                    threshold.CriticalHigh)),
                TimeSpan.FromMinutes(30));
        }
    }
}