using System.Text;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Minio.DataModel.Args;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

public sealed class DischargeSummaryWorkerService : BackgroundService
{
    private readonly IOptions<RabbitMqOptions> _rabbitOpts;
    private readonly IOptions<MinioOptions> _minioOpts;
    private readonly IServiceScopeFactory _scopes;
    private readonly ILogger<DischargeSummaryWorkerService> _logger;

    public DischargeSummaryWorkerService(
        IOptions<RabbitMqOptions> rabbitOpts,
        IOptions<MinioOptions> minioOpts,
        IServiceScopeFactory scopes,
        ILogger<DischargeSummaryWorkerService> logger)
    {
        _rabbitOpts = rabbitOpts;
        _minioOpts  = minioOpts;
        _scopes     = scopes;
        _logger     = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);

        var o       = _rabbitOpts.Value;
        var factory = RabbitMqConnectionFactory.Create(o, dispatchConsumersAsync: true);

        using var connection = factory.CreateConnection("discharge-summary-worker");
        using var channel    = connection.CreateModel();

        channel.BasicQos(0, prefetchCount: 3, global: false);

        var consumer = new AsyncEventingBasicConsumer(channel);
        consumer.Received += async (sender, ea) =>
        {
            await HandleDischargeSummaryAsync(channel, ea, stoppingToken);
        };

        channel.BasicConsume("notifications.discharge.queue", autoAck: false, consumer);

        _logger.LogInformation("DischargeSummaryWorkerService consuming notifications.discharge.queue");

        await Task.Delay(Timeout.Infinite, stoppingToken);
    }

    private async Task HandleDischargeSummaryAsync(IModel channel, BasicDeliverEventArgs ea, CancellationToken ct)
    {
        var payload     = Encoding.UTF8.GetString(ea.Body.Span);
        var doc         = JsonDocument.Parse(payload);
        var encounterId = Guid.Parse(doc.RootElement.GetProperty("encounterId").GetString()!);

        try
        {
            var summary = await BuildSummaryAsync(encounterId, ct);
            await UploadToMinioAsync(encounterId, summary, ct);

            channel.BasicAck(ea.DeliveryTag, multiple: false);

            _logger.LogInformation(
                "[DISCHARGE-SUMMARY] Uploaded for encounter {EncounterId}", encounterId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "DischargeSummaryWorker failed for encounter {EncounterId}", encounterId);
            channel.BasicNack(ea.DeliveryTag, multiple: false, requeue: true);
        }
    }

    private async Task<string> BuildSummaryAsync(Guid encounterId, CancellationToken ct)
    {
        await using var scope = _scopes.CreateAsyncScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var encounter = await db.Encounters
            .Include(e => e.Patient)
            .FirstOrDefaultAsync(e => e.Id == encounterId, ct);

        if (encounter is null)
            return $"DISCHARGE SUMMARY\nEncounter {encounterId} not found in database.";

        var obsCount = await db.Observations.CountAsync(o => o.EncounterId == encounterId, ct);
        var alertCount = await db.ClinicalAlerts.CountAsync(a => a.EncounterId == encounterId, ct);
        var orders = await db.Orders
            .Where(o => o.EncounterId == encounterId)
            .Select(o => new { o.OrderType, o.Description, o.Status, o.OrderedAt, o.ResultedAt })
            .ToListAsync(ct);

        var sb = new StringBuilder();
        sb.AppendLine("=== VIGILCARE DISCHARGE SUMMARY ===");
        sb.AppendLine($"Generated: {DateTimeOffset.UtcNow:u}");
        sb.AppendLine();
        sb.AppendLine($"Patient:            {encounter.Patient.FirstName} {encounter.Patient.LastName}");
        sb.AppendLine($"MRN:                {encounter.Patient.Mrn}");
        sb.AppendLine($"Date of Birth:      {encounter.Patient.DateOfBirth:yyyy-MM-dd}");
        sb.AppendLine();
        sb.AppendLine($"Encounter ID:       {encounter.Id}");
        sb.AppendLine($"Type:               {encounter.EncounterType}");
        sb.AppendLine($"Department:         {encounter.Department.ToDbString()}");
        sb.AppendLine($"Attending:          {encounter.AttendingPhysician}");
        sb.AppendLine($"Admitted:           {encounter.AdmittedAt:u}");
        sb.AppendLine($"Discharged:         {encounter.DischargedAt:u}");
        sb.AppendLine();
        sb.AppendLine($"Observations Recorded: {obsCount}");
        sb.AppendLine($"Clinical Alerts:       {alertCount}");
        sb.AppendLine();

        if (orders.Any())
        {
            sb.AppendLine("--- Orders ---");
            foreach (var o in orders)
                sb.AppendLine($"  [{o.Status.ToDbString()}] {o.OrderType.ToDbString()}: {o.Description} (ordered: {o.OrderedAt:u})");
        }

        sb.AppendLine();
        sb.AppendLine("NOTE: This summary is generated automatically. Clinician review required.");
        sb.AppendLine("NOTE: HIPAA Notice — This document contains protected health information.");

        return sb.ToString();
    }

    private async Task UploadToMinioAsync(Guid encounterId, string content, CancellationToken ct)
    {
        var mo     = _minioOpts.Value;
        var client = MinioClientFactory.Build(mo);
        var bucket = mo.BucketName;

        var exists = await client.BucketExistsAsync(
            new BucketExistsArgs().WithBucket(bucket), ct);

        if (!exists)
            await client.MakeBucketAsync(new MakeBucketArgs().WithBucket(bucket), ct);

        var objectKey = $"discharge-summaries/{encounterId}/summary.pdf";
        var bytes     = Encoding.UTF8.GetBytes(content);

        await client.PutObjectAsync(new PutObjectArgs()
            .WithBucket(bucket)
            .WithObject(objectKey)
            .WithStreamData(new MemoryStream(bytes))
            .WithObjectSize(bytes.Length)
            .WithContentType("text/plain"),
            ct);
    }
}