using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;

[Collection("Integration")]
public class News2DetectorTests : IAsyncLifetime
{
    private readonly ApiFixture _fixture;
    private Guid _encounterId;
    private Guid _patientId;

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

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

        var patient = new Patient
        {
            Id = Guid.NewGuid(), Mrn = "MRN-NEWS2-001", FirstName = "NEWS2", 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. NEWS2", AdmittedAt = DateTimeOffset.UtcNow,
            CreatedAt = DateTimeOffset.UtcNow
        };
        db.Patients.Add(patient);
        db.Encounters.Add(encounter);
        await db.SaveChangesAsync();

        _patientId = patient.Id;
        _encounterId = encounter.Id;

        var redis = scope.ServiceProvider.GetRequiredService<IConnectionMultiplexer>();
        var cache = redis.GetDatabase();
        foreach (var key in News2Calculator.AllParameterKeys(_encounterId))
            await cache.KeyDeleteAsync(key);
    }

    public Task DisposeAsync() => Task.CompletedTask;

    [Fact]
    public async Task AllNormalParameters_ScoreZero_NoAlert()
    {
        using var scope = _fixture.Services.CreateScope();
        var detector = scope.ServiceProvider.GetRequiredService<News2Detector>();

        await detector.ProcessObservationAsync(_encounterId, _patientId, "RESP_RATE", 16m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "SPO2", 98m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "SYSTOLIC_BP", 120m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "HEART_RATE", 72m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "AVPU", 0m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "TEMP_C", 36.8m);
        var result = await detector.ProcessObservationAsync(
            _encounterId, _patientId, "SUPPLEMENTAL_O2", 0m);

        result.Outcome.Should().Be(News2Outcome.ScoreComputed);
        result.TotalScore.Should().Be(0);
        result.RiskLevel.Should().Be("LOW");
        result.AlertCreated.Should().BeFalse();

        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var score = await db.News2Scores.SingleAsync();
        score.TotalScore.Should().Be(0);
        score.RiskLevel.Should().Be("LOW");
        (await db.ClinicalAlerts.CountAsync()).Should().Be(0);
    }

    [Fact]
    public async Task MediumRisk_WarningAlert()
    {
        using var scope = _fixture.Services.CreateScope();
        var detector = scope.ServiceProvider.GetRequiredService<News2Detector>();

        await detector.ProcessObservationAsync(_encounterId, _patientId, "RESP_RATE", 22m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "SPO2", 93m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "SYSTOLIC_BP", 105m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "HEART_RATE", 95m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "AVPU", 0m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "TEMP_C", 37.0m);
        var result = await detector.ProcessObservationAsync(
            _encounterId, _patientId, "SUPPLEMENTAL_O2", 0m);

        result.TotalScore.Should().Be(6);
        result.RiskLevel.Should().Be("MEDIUM");
        result.AlertCreated.Should().BeTrue();

        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var alert = await db.ClinicalAlerts.SingleAsync();
        alert.AlertType.Should().Be(AlertType.News2Warning);
        alert.Severity.Should().Be(AlertSeverity.Warning);
    }

    [Fact]
    public async Task HighRisk_CriticalAlert()
    {
        using var scope = _fixture.Services.CreateScope();
        var detector = scope.ServiceProvider.GetRequiredService<News2Detector>();

        await detector.ProcessObservationAsync(_encounterId, _patientId, "RESP_RATE", 25m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "SPO2", 91m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "SYSTOLIC_BP", 95m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "HEART_RATE", 72m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "AVPU", 0m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "TEMP_C", 37.0m);
        var result = await detector.ProcessObservationAsync(
            _encounterId, _patientId, "SUPPLEMENTAL_O2", 0m);

        result.TotalScore.Should().Be(8);
        result.RiskLevel.Should().Be("HIGH");
        result.AlertCreated.Should().BeTrue();

        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var alert = await db.ClinicalAlerts.SingleAsync();
        alert.AlertType.Should().Be(AlertType.News2Emergency);
        alert.Severity.Should().Be(AlertSeverity.Critical);
    }

    [Fact]
    public async Task SingleParamThree_LowMedium_Warning()
    {
        using var scope = _fixture.Services.CreateScope();
        var detector = scope.ServiceProvider.GetRequiredService<News2Detector>();

        await detector.ProcessObservationAsync(_encounterId, _patientId, "RESP_RATE", 8m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "SPO2", 98m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "SYSTOLIC_BP", 120m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "HEART_RATE", 72m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "AVPU", 0m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "TEMP_C", 37.0m);
        var result = await detector.ProcessObservationAsync(
            _encounterId, _patientId, "SUPPLEMENTAL_O2", 0m);

        result.TotalScore.Should().Be(3);
        result.RiskLevel.Should().Be("LOW_MEDIUM");
        result.AlertCreated.Should().BeTrue();
        result.HasSingleParamThree.Should().BeTrue();

        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var score = await db.News2Scores.SingleAsync();
        score.HasSingleParamThree.Should().BeTrue();

        var alert = await db.ClinicalAlerts.SingleAsync();
        alert.AlertType.Should().Be(AlertType.News2Warning);
    }

    [Fact]
    public async Task IncompleteParameters_NoScore()
    {
        using var scope = _fixture.Services.CreateScope();
        var detector = scope.ServiceProvider.GetRequiredService<News2Detector>();

        await detector.ProcessObservationAsync(_encounterId, _patientId, "RESP_RATE", 16m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "SPO2", 98m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "HEART_RATE", 72m);
        var result = await detector.ProcessObservationAsync(
            _encounterId, _patientId, "TEMP_C", 37.0m);

        result.Outcome.Should().Be(News2Outcome.IncompleteParameters);
        result.PresentParameters.Should().Be(4);

        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        (await db.News2Scores.CountAsync()).Should().Be(0);
        (await db.ClinicalAlerts.CountAsync()).Should().Be(0);
    }

    [Fact]
    public async Task NonNews2Code_Ignored()
    {
        using var scope = _fixture.Services.CreateScope();
        var detector = scope.ServiceProvider.GetRequiredService<News2Detector>();
        var redis = scope.ServiceProvider.GetRequiredService<IConnectionMultiplexer>();

        var result = await detector.ProcessObservationAsync(
            _encounterId, _patientId, "POTASSIUM_MEQ_L", 4.0m);

        result.Outcome.Should().Be(News2Outcome.NotNews2Code);

        var anyNews2Key = false;
        foreach (var key in News2Calculator.AllParameterKeys(_encounterId))
        {
            if (await redis.GetDatabase().KeyExistsAsync(key))
                anyNews2Key = true;
        }
        anyNews2Key.Should().BeFalse();
    }

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

        // First pass — score 8, creates alert
        await FeedHighRiskSet(detector);

        // Second scoring event — one parameter update while all 7 keys remain populated.
        // Each observation after completeness triggers a new score row; alert must stay deduplicated.
        await detector.ProcessObservationAsync(_encounterId, _patientId, "HEART_RATE", 72m);

        (await db.ClinicalAlerts.CountAsync()).Should().Be(1,
            "second NEWS2_EMERGENCY must be idempotent while first is still open");
        (await db.News2Scores.CountAsync()).Should().Be(2,
            "both scores should be persisted even though alert is deduplicated");
    }

    private async Task FeedHighRiskSet(News2Detector detector)
    {
        await detector.ProcessObservationAsync(_encounterId, _patientId, "RESP_RATE", 25m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "SPO2", 91m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "SYSTOLIC_BP", 95m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "HEART_RATE", 72m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "AVPU", 0m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "TEMP_C", 37.0m);
        await detector.ProcessObservationAsync(_encounterId, _patientId, "SUPPLEMENTAL_O2", 0m);
    }
}
