using Microsoft.EntityFrameworkCore;
using ROLAC.API.Data.Logging;
using ROLAC.API.Entities.Logging;

namespace ROLAC.API.Services.Logging;

/// <summary>
/// The single consumer that drains <see cref="SystemLogQueue"/> and batch-inserts rows through
/// the dedicated <see cref="LogDbContext"/> (a fresh DI scope per batch). Persistence failures
/// are swallowed to <c>Console.Error</c> only — they must never propagate back into the logging
/// pipeline or crash the host.
/// </summary>
public sealed class LogWriterBackgroundService : BackgroundService
{
    private const int    MaxBatchSize = 200;
    private static readonly TimeSpan FlushInterval = TimeSpan.FromSeconds(1);

    private readonly SystemLogQueue       _queue;
    private readonly IServiceScopeFactory _scopeFactory;

    public LogWriterBackgroundService(SystemLogQueue queue, IServiceScopeFactory scopeFactory)
    {
        _queue        = queue;
        _scopeFactory = scopeFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var systemBatch = new List<SystemLog>(MaxBatchSize);
        var auditBatch  = new List<AuditLog>(MaxBatchSize);

        try
        {
            await foreach (var envelope in _queue.ReadAllAsync(stoppingToken))
            {
                if (envelope.System is not null) systemBatch.Add(envelope.System);
                if (envelope.Audit  is not null) auditBatch.Add(envelope.Audit);

                // Coalesce a short burst into one round-trip; flush on size or a brief idle.
                if (systemBatch.Count + auditBatch.Count >= MaxBatchSize)
                {
                    await FlushAsync(systemBatch, auditBatch, stoppingToken);
                    continue;
                }

                if (!await WaitForMoreAsync(FlushInterval, stoppingToken))
                    await FlushAsync(systemBatch, auditBatch, stoppingToken);
            }
        }
        catch (OperationCanceledException)
        {
            // Shutting down — drain whatever is buffered.
        }

        await FlushAsync(systemBatch, auditBatch, CancellationToken.None);
    }

    /// <summary>Brief debounce so bursts coalesce; returns false once the window elapses.</summary>
    private static async Task<bool> WaitForMoreAsync(TimeSpan window, CancellationToken token)
    {
        try
        {
            await Task.Delay(window, token);
            return false;
        }
        catch (OperationCanceledException)
        {
            return false;
        }
    }

    private async Task FlushAsync(
        List<SystemLog> systemBatch, List<AuditLog> auditBatch, CancellationToken token)
    {
        if (systemBatch.Count == 0 && auditBatch.Count == 0)
            return;

        try
        {
            using var scope = _scopeFactory.CreateScope();
            var db = scope.ServiceProvider.GetRequiredService<LogDbContext>();

            if (systemBatch.Count > 0) db.SystemLogs.AddRange(systemBatch);
            if (auditBatch.Count  > 0) db.AuditLogs.AddRange(auditBatch);

            await db.SaveChangesAsync(token);
        }
        catch (Exception ex)
        {
            // Last resort: never throw out of the log writer. Include the inner exception —
            // the EF wrapper message alone ("An error occurred while saving...") hides the cause.
            var detail = ex.InnerException is null ? ex.Message : $"{ex.Message} -> {ex.InnerException.Message}";
            await Console.Error.WriteLineAsync(
                $"[LogWriter] Failed to persist {systemBatch.Count} system + {auditBatch.Count} audit rows: {detail}");
        }
        finally
        {
            systemBatch.Clear();
            auditBatch.Clear();
        }
    }
}
