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

public class GcsDetector
{
    private const int GcsTtlSeconds = 14400;  // 4 hours — same as NEWS2

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

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

    public async Task<GcsResult> ProcessObservationAsync(
        Guid encounterId,
        Guid patientId,
        string observationCode,
        decimal value,
        CancellationToken ct = default)
    {
        if (!GcsCalculator.IsGcsCode(observationCode))
            return GcsResult.NotGcsCode;

        var cache = _redis.GetDatabase();

        await cache.StringSetAsync(
            GcsCalculator.ComponentKey(encounterId, observationCode),
            value.ToString(CultureInfo.InvariantCulture),
            TimeSpan.FromSeconds(GcsTtlSeconds));

        var allKeys = GcsCalculator.AllComponentKeys(encounterId);
        var allValues = await cache.StringGetAsync(allKeys);

        if (allValues.Any(v => !v.HasValue))
        {
            var present = allValues.Count(v => v.HasValue);
            _logger.LogDebug(
                "GCS incomplete for encounter {Id}: {Present}/3 components present",
                encounterId, present);
            return GcsResult.IncompleteComponents(present);
        }

        var eye = decimal.Parse(allValues[0]!, CultureInfo.InvariantCulture);
        var verbal = decimal.Parse(allValues[1]!, CultureInfo.InvariantCulture);
        var motor = decimal.Parse(allValues[2]!, CultureInfo.InvariantCulture);
        var total = GcsCalculator.ComputeTotal(eye, verbal, motor)!.Value;
        var classification = GcsCalculator.ClassifyGcs(total);
        var calculatedAt = DateTimeOffset.UtcNow;

        using (var checkScope = _services.CreateScope())
        {
            var db = checkScope.ServiceProvider.GetRequiredService<AppDbContext>();
            if (!await db.Encounters.AnyAsync(e => e.Id == encounterId, ct))
            {
                _logger.LogWarning(
                    "Skipping GCS score for unknown encounter {EncounterId}", encounterId);
                return GcsResult.EncounterNotFound;
            }
        }

        await PersistScoreAsync(
            encounterId, patientId, (int)eye, (int)verbal, (int)motor,
            total, classification, calculatedAt, ct);

        var contributors = GcsContributorBuilder.Build((int)eye, (int)verbal, (int)motor);

        var alertCreated = false;
        if (total <= 8)
        {
            alertCreated = await TryCreateAlertAsync(
                encounterId, patientId, AlertType.GcsCritical, AlertSeverity.Critical,
                eye, verbal, motor, total, classification, contributors, ct);
        }
        else if (total <= 12)
        {
            alertCreated = await TryCreateAlertAsync(
                encounterId, patientId, AlertType.GcsWarning, AlertSeverity.Warning,
                eye, verbal, motor, total, classification, contributors, ct);
        }

        await PublishScoredEventAsync(
            encounterId, patientId, eye, verbal, motor, total, classification, calculatedAt, ct);

        // Re-evaluate qSOFA altered mentation from the computed GCS total (Step 6)
        using (var scope = _services.CreateScope())
        {
            var qsofa = scope.ServiceProvider.GetRequiredService<QsofaDetector>();
            await qsofa.SyncAlteredMentationAsync(encounterId, patientId, ct);
        }

        _metrics.GcsScoresTotal.WithLabels(classification).Inc();

        _logger.LogInformation(
            "GCS score {Total} ({Classification}) for encounter {Id} — E={Eye} V={Verbal} M={Motor}",
            total, classification, encounterId, eye, verbal, motor);

        return new GcsResult(
            GcsOutcome.ScoreComputed, total, classification, alertCreated, 3, contributors);
    }

    private async Task PersistScoreAsync(
        Guid encounterId, Guid patientId,
        int eye, int verbal, int motor,
        int total, string classification,
        DateTimeOffset calculatedAt,
        CancellationToken ct)
    {
        using var scope = _services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        db.GcsScores.Add(new GcsScore
        {
            Id = Guid.NewGuid(),
            EncounterId = encounterId,
            PatientId = patientId,
            EyeScore = eye,
            VerbalScore = verbal,
            MotorScore = motor,
            TotalScore = total,
            Classification = classification,
            CalculatedAt = calculatedAt,
            CreatedAt = DateTimeOffset.UtcNow
        });

        await db.SaveChangesAsync(ct);
    }

    private async Task<bool> TryCreateAlertAsync(
        Guid encounterId, Guid patientId,
        AlertType alertType, AlertSeverity severity,
        decimal eye, decimal verbal, decimal motor,
        int total, string classification,
        List<ScoreContributor> contributors,
        CancellationToken ct)
    {
        if (alertType == AlertType.GcsWarning)
        {
            var suppression = _services.GetRequiredService<IAlertSuppressionService>();
            if (await suppression.IsSuppressedAsync(encounterId, alertType, ct))
            {
                _logger.LogDebug("GCS_WARNING suppressed for encounter {Id}", encounterId);
                return false;
            }
        }

        AlertCreationGuard.EnsureAllowed(alertType);

        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 details =
            $"GCS total {total} ({classification}): E={eye}, V={verbal}, M={motor}.";

        var explanation = AlertExplanationBuilder.Build(
            "GCS", total, contributors);

        var inserted = await ClinicalAlertFactory.TryInsertAsync(
            db, alertId, encounterId, patientId,
            alertType, severity, details, explanation,
            observationCode: null, triggeredAt, ct);

        if (!inserted)
        {
            await tx.RollbackAsync(ct);
            return false;
        }

        db.OutboxEvents.Add(new OutboxEvent
        {
            Id = Guid.NewGuid(),
            Topic = "alert.generated",
            Payload = ClinicalAlertFactory.SerializeOutboxPayload(new
            {
                alertId,
                encounterId,
                patientId,
                alertType = alertType.ToDbString(),
                severity = severity.ToDbString(),
                details,
                triggeredAt,
                gcsTotal = total,
                gcsClassification = classification,
                explanation,
                partitionKey = encounterId.ToString()
            }),
            PartitionKey = encounterId.ToString(),
            CreatedAt = DateTimeOffset.UtcNow
        });

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

        _metrics.ClinicalAlertsTotal
            .WithLabels(alertType.ToDbString(), severity.ToDbString()).Inc();

        return true;
    }

    private async Task PublishScoredEventAsync(
        Guid encounterId, Guid patientId,
        decimal eye, decimal verbal, decimal motor,
        int total, string classification,
        DateTimeOffset calculatedAt,
        CancellationToken ct)
    {
        using var scope = _services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        db.OutboxEvents.Add(new OutboxEvent
        {
            Id = Guid.NewGuid(),
            Topic = "gcs.scored",
            Payload = JsonSerializer.Serialize(new
            {
                encounterId,
                patientId,
                eyeScore = (int)eye,
                verbalScore = (int)verbal,
                motorScore = (int)motor,
                totalScore = total,
                classification,
                calculatedAt,
                partitionKey = encounterId.ToString()
            }),
            PartitionKey = encounterId.ToString(),
            CreatedAt = DateTimeOffset.UtcNow
        });

        await db.SaveChangesAsync(ct);
    }
}