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

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

    public GcsScoringTests(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 patient = new Patient
        {
            Id = Guid.NewGuid(), Mrn = "MRN-GCS-001", FirstName = "GCS", 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. GCS", 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 GcsCalculator.AllComponentKeys(_encounterId))
            await cache.KeyDeleteAsync(key);
    }

    public Task DisposeAsync() => Task.CompletedTask;

    private async Task<GcsResult> ScoreAsync(string code, decimal value)
    {
        using var scope = _fixture.Services.CreateScope();
        var detector = scope.ServiceProvider.GetRequiredService<GcsDetector>();
        return await detector.ProcessObservationAsync(_encounterId, _patientId, code, value);
    }

    [Fact]
    public async Task IngestThreeComponents_ComputesTotal()
    {
        await ScoreAsync("GCS_EYE", 4m);
        await ScoreAsync("GCS_VERBAL", 5m);
        var result = await ScoreAsync("GCS_MOTOR", 6m);

        result.Outcome.Should().Be(GcsOutcome.ScoreComputed);
        result.TotalScore.Should().Be(15);
        result.Classification.Should().Be("MILD");

        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var score = await db.GcsScores.SingleAsync();
        score.TotalScore.Should().Be(15);
    }

    [Fact]
    public async Task GcsTotal8_CreatesCriticalAlert()
    {
        await ScoreAsync("GCS_EYE", 2m);
        await ScoreAsync("GCS_VERBAL", 3m);
        var result = await ScoreAsync("GCS_MOTOR", 3m);

        result.TotalScore.Should().Be(8);
        result.AlertCreated.Should().BeTrue();

        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var alert = await db.ClinicalAlerts.SingleAsync();
        alert.AlertType.Should().Be(AlertType.GcsCritical);
        alert.Severity.Should().Be(AlertSeverity.Critical);
        alert.AlertType.IsSuppressible().Should().BeFalse();
    }

    [Fact]
    public async Task GcsTotal12_CreatesWarningAlert()
    {
        await ScoreAsync("GCS_EYE", 3m);
        await ScoreAsync("GCS_VERBAL", 4m);
        var result = await ScoreAsync("GCS_MOTOR", 5m);

        result.TotalScore.Should().Be(12);
        result.AlertCreated.Should().BeTrue();

        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var alert = await db.ClinicalAlerts.SingleAsync();
        alert.AlertType.Should().Be(AlertType.GcsWarning);
        alert.Severity.Should().Be(AlertSeverity.Warning);
        alert.AlertType.IsSuppressible().Should().BeTrue();
    }

    [Fact]
    public async Task GcsTotal15_NoAlert()
    {
        await ScoreAsync("GCS_EYE", 4m);
        await ScoreAsync("GCS_VERBAL", 5m);
        await ScoreAsync("GCS_MOTOR", 6m);

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

    [Fact]
    public async Task PartialComponents_NoScore()
    {
        await ScoreAsync("GCS_EYE", 4m);
        await ScoreAsync("GCS_VERBAL", 5m);

        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        (await db.GcsScores.CountAsync()).Should().Be(0);
    }

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

        await news2.ProcessObservationAsync(_encounterId, _patientId, "RESP_RATE", 16m);
        await news2.ProcessObservationAsync(_encounterId, _patientId, "SPO2", 98m);
        await news2.ProcessObservationAsync(_encounterId, _patientId, "SYSTOLIC_BP", 120m);
        await news2.ProcessObservationAsync(_encounterId, _patientId, "HEART_RATE", 72m);
        await news2.ProcessObservationAsync(_encounterId, _patientId, "TEMP_C", 36.8m);
        await news2.ProcessObservationAsync(_encounterId, _patientId, "SUPPLEMENTAL_O2", 0m);

        await ScoreAsync("GCS_EYE", 2m);
        await ScoreAsync("GCS_VERBAL", 3m);
        await ScoreAsync("GCS_MOTOR", 4m);

        var result = await news2.ProcessObservationAsync(_encounterId, _patientId, "GCS_MOTOR", 4m);
        result.Outcome.Should().Be(News2Outcome.ScoreComputed);

        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var score = await db.News2Scores.OrderByDescending(s => s.CalculatedAt).FirstAsync();
        score.ConsciousnessScore.Should().Be(3);
    }

    [Fact]
    public async Task GcsTriggersQsofaReeval()
    {
        using var scope = _fixture.Services.CreateScope();
        var qsofa = scope.ServiceProvider.GetRequiredService<QsofaDetector>();

        await qsofa.ProcessObservationAsync(_encounterId, _patientId, "RESP_RATE", 24m);

        await ScoreAsync("GCS_EYE", 2m);
        await ScoreAsync("GCS_VERBAL", 3m);
        await ScoreAsync("GCS_MOTOR", 4m);

        var redis = scope.ServiceProvider.GetRequiredService<IConnectionMultiplexer>();
        var avpuKey = QsofaCalculator.CriterionKey(_encounterId, "AVPU");
        var exists = await redis.GetDatabase().KeyExistsAsync(avpuKey);
        exists.Should().BeTrue("GCS total 9 should set altered mentation criterion");
    }

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

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

        result.Outcome.Should().Be(News2Outcome.ScoreComputed);
        result.TotalScore.Should().Be(0);

        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var score = await db.News2Scores.SingleAsync();
        score.ConsciousnessScore.Should().Be(0);
    }

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

        db.GcsScores.AddRange(
            new GcsScore
            {
                Id = Guid.NewGuid(),
                EncounterId = _encounterId,
                PatientId = _patientId,
                EyeScore = 4,
                VerbalScore = 5,
                MotorScore = 6,
                TotalScore = 15,
                Classification = "MILD",
                CalculatedAt = now.AddHours(-2),
                CreatedAt = now,
            },
            new GcsScore
            {
                Id = Guid.NewGuid(),
                EncounterId = _encounterId,
                PatientId = _patientId,
                EyeScore = 2,
                VerbalScore = 3,
                MotorScore = 3,
                TotalScore = 8,
                Classification = "SEVERE",
                CalculatedAt = now,
                CreatedAt = now,
            });
        await db.SaveChangesAsync();

        var resp = await _client.GetAsync(
            $"/api/v1/encounters/{_encounterId}/gcs/history?limit=10");
        resp.StatusCode.Should().Be(HttpStatusCode.OK);

        var body = await resp.Content.ReadFromJsonAsync<JsonElement>();
        var items = body.GetProperty("data").GetProperty("items");
        items.GetArrayLength().Should().Be(2);

        var latest = items[0];
        latest.GetProperty("totalScore").GetInt32().Should().Be(8);
        latest.GetProperty("eyeScore").GetInt32().Should().Be(2);
        latest.GetProperty("verbalScore").GetInt32().Should().Be(3);
        latest.GetProperty("motorScore").GetInt32().Should().Be(3);

        var earliest = items[1];
        earliest.GetProperty("totalScore").GetInt32().Should().Be(15);
    }
}