using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Serilog.Context;
using StackExchange.Redis;

public class QsofaDetector
{
    private const int QsofaTtlSeconds = 1800;

    private readonly IConnectionMultiplexer _redis;
    private readonly IServiceProvider _services;
    private readonly ILogger<QsofaDetector> _logger;
    private readonly ClinicalMetrics _metrics;

    public QsofaDetector(
        IConnectionMultiplexer redis,
        IServiceProvider services,
        ILogger<QsofaDetector> logger,
        ClinicalMetrics metrics)
    {
        _redis = redis;
        _services = services;
        _logger = logger;
        _metrics = metrics;
    }

    public async Task<QsofaResult> ProcessObservationAsync(
        Guid encounterId,
        Guid patientId,
        string observationCode,
        decimal value,
        CancellationToken ct = default)
    {
        if (!QsofaCalculator.QsofaCodeSet.Contains(observationCode))
            return QsofaResult.NotQsofaCode;

        var cache = _redis.GetDatabase();
        var key = QsofaCalculator.CriterionKey(encounterId, observationCode);

        if (QsofaCalculator.MeetsCriterion(observationCode, value))
        {
            await cache.StringSetAsync(
                key, value.ToString(), TimeSpan.FromSeconds(QsofaTtlSeconds));
        }
        else
        {
            await cache.KeyDeleteAsync(key);
        }

        return await EvaluateAndMaybeAlertAsync(encounterId, patientId, ct);
    }

    public async Task<QsofaResult> SyncAlteredMentationAsync(
        Guid encounterId,
        Guid patientId,
        CancellationToken ct = default)
    {
        var cache = _redis.GetDatabase();
        var avpuKey = QsofaCalculator.CriterionKey(encounterId, "AVPU");
        var gcsValues = await cache.StringGetAsync(GcsCalculator.AllComponentKeys(encounterId));

        if (gcsValues.All(v => v.HasValue))
        {
            var eye = decimal.Parse(gcsValues[0]!);
            var verbal = decimal.Parse(gcsValues[1]!);
            var motor = decimal.Parse(gcsValues[2]!);
            var total = GcsCalculator.ComputeTotal(eye, verbal, motor)!.Value;

            if (QsofaCalculator.MeetsGcsAlteredMentation(total))
                await cache.StringSetAsync(avpuKey, "1", TimeSpan.FromSeconds(QsofaTtlSeconds));
            else
                await cache.KeyDeleteAsync(avpuKey);
        }

        return await EvaluateAndMaybeAlertAsync(encounterId, patientId, ct);
    }

    private async Task<QsofaResult> EvaluateAndMaybeAlertAsync(
        Guid encounterId, Guid patientId, CancellationToken ct)
    {
        var cache = _redis.GetDatabase();
        var allKeys = QsofaCalculator.AllCriterionKeys(encounterId);
        var values = await cache.StringGetAsync(allKeys);
        var activeCount = QsofaCalculator.CountActiveCriteria(values);

        var created = activeCount >= 2
            && await TryCreateScreenAlertAsync(encounterId, patientId, activeCount, values, ct);

        await PersistEvaluationIfChangedAsync(
            encounterId, patientId, activeCount, values, created, ct);

        if (activeCount < 2)
            return QsofaResult.InsufficientCriteria(activeCount);

        return created ? QsofaResult.AlertCreated : QsofaResult.AlertAlreadyOpen;
    }

    private async Task PersistEvaluationIfChangedAsync(
        Guid encounterId,
        Guid patientId,
        int activeCount,
        RedisValue[] values,
        bool screenAlertFired,
        CancellationToken ct)
    {
        using var scope = _services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var last = await db.QsofaEvaluations
            .AsNoTracking()
            .Where(e => e.EncounterId == encounterId)
            .OrderByDescending(e => e.EvaluatedAt)
            .ThenByDescending(e => e.Id)
            .Select(e => new { e.ActiveCriteria, e.ScreenAlertFired })
            .FirstOrDefaultAsync(ct);

        if (last is not null
            && last.ActiveCriteria == activeCount
            && !screenAlertFired)
        {
            return;
        }

        var criteria = QsofaCalculator.ParseCriteriaState(values);
        var evaluatedAt = DateTimeOffset.UtcNow;

        db.QsofaEvaluations.Add(new QsofaEvaluation
        {
            Id = Guid.NewGuid(),
            EncounterId = encounterId,
            PatientId = patientId,
            ActiveCriteria = activeCount,
            RespRate = criteria.RespRate,
            SystolicBp = criteria.SystolicBp,
            Avpu = criteria.Avpu,
            ScreenAlertFired = screenAlertFired,
            EvaluatedAt = evaluatedAt,
            CreatedAt = evaluatedAt,
        });

        await db.SaveChangesAsync(ct);
    }

    private async Task<bool> TryCreateScreenAlertAsync(
        Guid encounterId,
        Guid patientId,
        int activeCount,
        RedisValue[] criterionValues,
        CancellationToken ct)
    {
        AlertCreationGuard.EnsureAllowed(AlertType.QsofaScreen);

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

        await using var tx = await db.Database.BeginTransactionAsync(ct);

        var alertId = Guid.NewGuid();
        var triggeredAt = DateTimeOffset.UtcNow;
        var activeCriteria = BuildActiveCriteriaList(criterionValues);
        var details =
            $"qSOFA score ≥ 2 (criteria: {activeCriteria}). " +
            "Recommend: order SOFA labs (PaO2/FiO2, platelets, bilirubin, creatinine) " +
            "to evaluate for organ dysfunction.";

        var affected = await db.Database.ExecuteSqlInterpolatedAsync($"""
            INSERT INTO clinical_alerts
                (id, encounter_id, patient_id, alert_type, severity, details, status, triggered_at)
            SELECT {alertId}, {encounterId}, {patientId},
                   'QSOFA_SCREEN', 'WARNING', {details}, 'OPEN', {triggeredAt}
            WHERE NOT EXISTS (
                SELECT 1 FROM clinical_alerts
                WHERE  encounter_id = {encounterId}
                  AND  alert_type   = 'QSOFA_SCREEN'
                  AND  status       IN ('OPEN', 'ESCALATED')
            )
            """, ct);

        if (affected == 0)
        {
            await tx.RollbackAsync(ct);
            return false;
        }

        db.OutboxEvents.Add(new OutboxEvent
        {
            Id = Guid.NewGuid(),
            Topic = "alert.generated",
            Payload = JsonSerializer.Serialize(new
            {
                alertId,
                encounterId,
                patientId,
                alertType = AlertType.QsofaScreen.ToDbString(),
                severity = AlertSeverity.Warning.ToDbString(),
                details,
                triggeredAt,
                partitionKey = encounterId.ToString()
            }),
            PartitionKey = encounterId.ToString(),
            CreatedAt = DateTimeOffset.UtcNow
        });

        await db.SaveChangesAsync(ct);
        await tx.CommitAsync(ct);

        _metrics.QsofaDetectionsTotal.Inc();
        _metrics.ClinicalAlertsTotal
            .WithLabels(AlertType.QsofaScreen.ToDbString(), AlertSeverity.Warning.ToDbString())
            .Inc();

        using (LogContext.PushProperty("EncounterId", encounterId))
        using (LogContext.PushProperty("PatientId", patientId))
        {
            _logger.LogWarning(
                "QSOFA_SCREEN created. ActiveCriteriaCount={Count} AlertId={AlertId}",
                activeCount, alertId);
        }

        // Screening alert — does NOT trigger sepsis bundle (Phase 27 Step 4)
        return true;
    }

    private static string BuildActiveCriteriaList(RedisValue[] values)
    {
        var activeParts = new List<string>();
        for (var i = 0; i < QsofaCalculator.QsofaCodes.Count; i++)
        {
            if (values[i].HasValue)
                activeParts.Add($"{QsofaCalculator.QsofaCodes[i]}={values[i]}");
        }
        return string.Join(", ", activeParts);
    }
}