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

public class SepsisBundleService : ISepsisBundleService
{
    private readonly AppDbContext _db;
    private readonly IOrderService _orderService;
    private readonly ClinicalMetrics _metrics;

    public SepsisBundleService(AppDbContext db, IOrderService orderService, ClinicalMetrics metrics)
    {
        _db = db;
        _orderService = orderService;
        _metrics = metrics;
    }

    public async Task<SepsisBundle?> TryCreateBundleAsync(
        Guid encounterId, Guid triggeringAlertId, AlertType alertType, CancellationToken ct = default)
    {
        if (alertType != AlertType.SofaSepsis)
            throw new InvalidOperationException(
                $"Sepsis bundle can only be triggered by SOFA_SEPSIS, not {alertType.ToDbString()}.");

        var exists = await _db.SepsisBundles
            .AnyAsync(b => b.EncounterId == encounterId
                && b.ComplianceStatus == SepsisBundleComplianceStatus.InProgress, ct);

        if (exists)
            return null;

        await using var tx = await _db.Database.BeginTransactionAsync(ct);
        try
        {
            var recognizedAt = DateTimeOffset.UtcNow;
            var bundle = new SepsisBundle
            {
                Id = Guid.NewGuid(),
                EncounterId = encounterId,
                TriggeringAlertId = triggeringAlertId,
                TriggeringAlertType = alertType.ToDbString(),
                RecognizedAt = recognizedAt,
                DeadlineAt = recognizedAt.AddHours(1),
                ComplianceStatus = SepsisBundleComplianceStatus.InProgress
            };

            var elementDefs = new (SepsisBundleElementType Type, OrderType OrderType, string Description)[]
            {
                (SepsisBundleElementType.BloodCultures,            OrderType.Lab,        "SEP-1: Blood cultures"),
                (SepsisBundleElementType.SerumLactate,             OrderType.Lab,        "SEP-1: Serum lactate"),
                (SepsisBundleElementType.BroadSpectrumAntibiotics, OrderType.Medication, "SEP-1: Broad-spectrum antibiotics"),
                (SepsisBundleElementType.IvFluidResuscitation,     OrderType.Procedure,  "SEP-1: IV fluid bolus"),
            };

            foreach (var (elementType, orderType, description) in elementDefs)
            {
                var order = await _orderService.CreateAsync(encounterId, new CreateOrderRequest(
                    orderType, description, "sepsis-bundle-engine"));

                bundle.Elements.Add(new SepsisBundleElement
                {
                    Id = Guid.NewGuid(),
                    ElementType = elementType,
                    Status = SepsisBundleElementStatus.Pending,
                    OrderId = order.Id
                });
            }

            _db.SepsisBundles.Add(bundle);

            _db.OutboxEvents.Add(new OutboxEvent
            {
                Id = Guid.NewGuid(),
                Topic = "sepsis.bundle.created",
                Payload = JsonSerializer.Serialize(new
                {
                    bundleId = bundle.Id,
                    encounterId,
                    triggeringAlertId,
                    triggeringAlertType = alertType.ToDbString(),
                    recognizedAt,
                    deadlineAt = bundle.DeadlineAt,
                    partitionKey = encounterId.ToString()
                }),
                PartitionKey = encounterId.ToString(),
                CreatedAt = DateTimeOffset.UtcNow
            });

            await _db.SaveChangesAsync(ct);
            await tx.CommitAsync(ct);
            return bundle;
        }
        catch (DbUpdateException ex)
            when (ex.InnerException is Npgsql.PostgresException { SqlState: "23505" } pg
                  && pg.ConstraintName == "ix_sepsis_bundles_encounter_in_progress")
        {
            await tx.RollbackAsync(ct);
            _db.ChangeTracker.Clear();
            return null;
        }
    }

    public async Task<SepsisBundle?> GetCurrentByEncounterAsync(Guid encounterId)
    {
        return await _db.SepsisBundles
            .AsNoTracking()
            .Include(b => b.Elements)
                .ThenInclude(e => e.Order)
            .Where(b => b.EncounterId == encounterId)
            .OrderByDescending(b => b.RecognizedAt)
            .FirstOrDefaultAsync();
    }

    public async Task<SepsisBundle> GetByIdAsync(Guid id)
    {
        var bundle = await _db.SepsisBundles
            .AsNoTracking()
            .Include(b => b.Elements)
                .ThenInclude(e => e.Order)
            .FirstOrDefaultAsync(b => b.Id == id);

        if (bundle is null)
            throw new NotFoundException("Sepsis bundle not found.", "BUNDLE_NOT_FOUND");

        return bundle;
    }

    public async Task<PagedResult<SepsisBundleSummary>> ListAsync(
        SepsisBundleComplianceStatus? status, int page, int pageSize)
    {
        pageSize = Math.Clamp(pageSize, 1, 100);

        var query = _db.SepsisBundles
            .AsNoTracking()
            .Include(b => b.Elements)
            .Include(b => b.Encounter)
                .ThenInclude(e => e.Patient)
            .AsQueryable();

        if (status.HasValue)
            query = query.Where(b => b.ComplianceStatus == status.Value);

        var total = await query.CountAsync();
        var bundles = await query
            .OrderByDescending(b => b.RecognizedAt)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();

        var items = bundles.Select(b => new SepsisBundleSummary(
            b.Id,
            b.EncounterId,
            b.Encounter.Patient.Mrn,
            b.Encounter.Patient.FirstName,
            b.Encounter.Patient.LastName,
            b.Encounter.RoomBed,
            b.Encounter.Department,
            b.TriggeringAlertType,
            b.RecognizedAt,
            b.DeadlineAt,
            b.ComplianceStatus,
            b.CompletedAt,
            b.Elements
                .Select(e => new SepsisBundleElementSummary(e.Id, e.ElementType, e.Status, e.CompletedAt))
                .ToList()))
            .ToList();

        return new PagedResult<SepsisBundleSummary>(items, page, pageSize, total);
    }

    public async Task OnOrderResultedAsync(Guid orderId, CancellationToken ct = default)
    {
        var element = await _db.SepsisBundleElements
            .Include(e => e.Bundle)
            .FirstOrDefaultAsync(e => e.OrderId == orderId, ct);

        if (element is null || element.Status == SepsisBundleElementStatus.Completed)
            return;

        element.Status = SepsisBundleElementStatus.Completed;
        element.CompletedAt = DateTimeOffset.UtcNow;

        var bundle = element.Bundle;
        var allComplete = await _db.SepsisBundleElements
            .Where(e => e.BundleId == bundle.Id && e.Id != element.Id)
            .AllAsync(e => e.Status == SepsisBundleElementStatus.Completed, ct);

        if (allComplete)
        {
            bundle.CompletedAt = DateTimeOffset.UtcNow;
            bundle.ComplianceStatus = bundle.CompletedAt <= bundle.DeadlineAt
                ? SepsisBundleComplianceStatus.Compliant
                : SepsisBundleComplianceStatus.NonCompliant;

            _metrics.SepsisBundleComplianceTotal
                .WithLabels(bundle.ComplianceStatus.ToDbString()).Inc();
        }

        _db.OutboxEvents.Add(new OutboxEvent
        {
            Id = Guid.NewGuid(),
            Topic = "sepsis.bundle.updated",
            Payload = JsonSerializer.Serialize(new
            {
                bundleId = bundle.Id,
                encounterId = bundle.EncounterId,
                elementId = element.Id,
                elementType = element.ElementType.ToDbString(),
                elementStatus = element.Status.ToDbString(),
                complianceStatus = bundle.ComplianceStatus.ToDbString(),
                completedAt = element.CompletedAt,
                partitionKey = bundle.EncounterId.ToString()
            }),
            PartitionKey = bundle.EncounterId.ToString(),
            CreatedAt = DateTimeOffset.UtcNow
        });

        await _db.SaveChangesAsync(ct);
    }
    
}
