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

public class WarningEvaluator
{
    private readonly IConnectionMultiplexer _redis;
    private readonly IServiceProvider _services;
    private readonly ILogger<WarningEvaluator> _logger;

    public WarningEvaluator(
        IConnectionMultiplexer redis,
        IServiceProvider services,
        ILogger<WarningEvaluator> logger)
    {
        _redis = redis;
        _services = services;
        _logger = logger;
    }

    public async Task<bool> EvaluateAsync(
        Guid observationId,
        Guid encounterId,
        Guid patientId,
        string observationCode,
        decimal value,
        CancellationToken ct = default)
    {
        var threshold = await LoadThresholdAsync(observationCode);
        if (threshold is null) return false;

        if (!IsWarningBreach(value, threshold)) return false;

        // Do not create a warning if the value is also a critical breach —
        // critical alerts are created synchronously by the ingest path.
        if (IsCriticalBreach(value, threshold)) return false;

        var alertType = AlertTypeExtensions.WarningFor(observationCode);
        var suppression = _services.GetRequiredService<IAlertSuppressionService>();
        if (await suppression.IsSuppressedAsync(encounterId, alertType, ct))
        {
            _logger.LogDebug(
                "Warning alert suppressed for encounter {Id} type {Type}", encounterId, alertType);
            return false;
        }

        return await TryCreateWarningAlertAsync(
            observationId, encounterId, patientId, observationCode, value, threshold, ct);
    }

    private static bool IsWarningBreach(decimal value, ThresholdCacheEntry t) =>
        (t.WarningHigh.HasValue && value > t.WarningHigh.Value) ||
        (t.WarningLow.HasValue && value < t.WarningLow.Value);

    private static bool IsCriticalBreach(decimal value, ThresholdCacheEntry t) =>
        (t.CriticalLow.HasValue && value < t.CriticalLow.Value) ||
        (t.CriticalHigh.HasValue && value > t.CriticalHigh.Value);

    private async Task<ThresholdCacheEntry?> LoadThresholdAsync(string observationCode)
    {
        var cache = _redis.GetDatabase();
        var cached = await cache.StringGetAsync($"threshold:{observationCode}");
        if (cached.HasValue)
            return JsonSerializer.Deserialize<ThresholdCacheEntry>(cached!);
        return null;
    }

    // Idempotent INSERT: prevents duplicate warning alerts for the same observation.
    // The WHERE NOT EXISTS checks for an open warning alert of the same type for the
    // same encounter. Unlike critical alerts (one per encounter), warning alerts are
    // expected to recur — but not for every single observation in a series. If the
    // patient's heart rate stays at 105 bpm for an hour, one WARNING_HEART_RATE is
    // sufficient until acknowledged or resolved.
    private async Task<bool> TryCreateWarningAlertAsync(
        Guid observationId,
        Guid encounterId,
        Guid patientId,
        string observationCode,
        decimal value,
        ThresholdCacheEntry threshold,
        CancellationToken ct)
    {
        using var scope = _services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

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

        var alertType = AlertTypeExtensions.WarningFor(observationCode);
        var alertId = Guid.NewGuid();
        var triggeredAt = DateTimeOffset.UtcNow;
        var details = BuildWarningDetails(observationCode, value, threshold);

        var correlation = scope.ServiceProvider.GetRequiredService<MedicationCorrelationHelper>();
        details = await correlation.TryAnnotateDetailsAsync(
            encounterId, observationCode, details, ct);

        var affected = await db.Database.ExecuteSqlInterpolatedAsync($"""
            INSERT INTO clinical_alerts
                (id, encounter_id, patient_id, observation_id, alert_type, severity, details, status, triggered_at)
            SELECT {alertId}, {encounterId}, {patientId}, {observationId},
                   {alertType.ToDbString()}, 'WARNING', {details}, 'OPEN', {triggeredAt}
            WHERE NOT EXISTS (
                SELECT 1 FROM clinical_alerts
                WHERE  encounter_id = {encounterId}
                  AND  alert_type   = {alertType.ToDbString()}
                  AND  status       IN ('OPEN', 'ACKNOWLEDGED')
            )
            """, 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.ToDbString(),
                severity = "Warning",
                details,
                triggeredAt,
                partitionKey = encounterId.ToString()
            }),
            PartitionKey = encounterId.ToString(),
            CreatedAt = DateTimeOffset.UtcNow
        });

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

        _logger.LogInformation(
            "WARNING alert {AlertId} created for encounter {EncounterId} — {Code}={Value}",
            alertId, encounterId, observationCode, value);

        return true;
    }

    private static string BuildWarningDetails(
        string code, decimal value, ThresholdCacheEntry t)
    {
        if (t.WarningHigh.HasValue && value > t.WarningHigh.Value)
            return $"{code} value {value} is above warning high of {t.WarningHigh}.";
        return $"{code} value {value} is below warning low of {t.WarningLow}.";
    }
}