using EonaCat.Logger.Managers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace EonaCat.Logger.EonaCatCoreLogger.Internal;

public abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
{
    private readonly int _batchSize;
    private readonly BlockingCollection<LogMessage> _queue;
    private readonly CancellationTokenSource _cts = new();
    private readonly Task _worker;

    private bool _disposed;
    private LoggerSettings _loggerSettings;

    protected BatchingLoggerProvider(IOptions<BatchingLoggerOptions> options)
    {
        var currentOptions = options.Value ?? throw new ArgumentNullException(nameof(options));

        if (currentOptions.FlushPeriod <= TimeSpan.Zero)
        {
            throw new ArgumentOutOfRangeException(nameof(currentOptions.FlushPeriod));
        }

        _batchSize = currentOptions.BatchSize > 0 ? currentOptions.BatchSize : 100;
        _queue = new BlockingCollection<LogMessage>(Math.Max(1, _batchSize * 2));

        if (currentOptions is FileLoggerOptions file)
        {
            UseLocalTime = file.UseLocalTime;
            UseMask = file.UseMask;
            LoggerSettings = file.LoggerSettings;
        }

        _worker = Task.Factory.StartNew(
            ProcessLoop,
            _cts.Token,
            TaskCreationOptions.LongRunning,
            TaskScheduler.Default);
    }

    protected bool UseLocalTime { get; }
    public bool UseMask { get; }

    protected DateTimeOffset NowOffset =>
        UseLocalTime ? DateTimeOffset.Now : DateTimeOffset.UtcNow;

    protected LoggerSettings LoggerSettings
    {
        get
        {
            if (_loggerSettings == null)
            {
                _loggerSettings = new LoggerSettings
                {
                    UseLocalTime = UseLocalTime,
                    UseMask = UseMask
                };
            }
            return _loggerSettings;
        }
        set => _loggerSettings = value;
    }

    public ILogger CreateLogger(string categoryName)
        => new BatchingLogger(this, categoryName, LoggerSettings);

    internal abstract Task WriteMessagesAsync(
        IReadOnlyList<LogMessage> messages,
        CancellationToken token);

    internal string AddMessage(DateTimeOffset timestamp, string message, string category)
    {
        var log = CreateLogMessage(message, timestamp, category);
        _queue.Add(log);
        return log.Message;
    }

    private LogMessage CreateLogMessage(string message, DateTimeOffset ts, string category)
    {
        if (LoggerSettings.UseMask)
        {
            SensitiveDataMasker sensitiveDataMasker = new SensitiveDataMasker(LoggerSettings);
            message = sensitiveDataMasker.MaskSensitiveInformation(message);
        }

        return new LogMessage
        {
            Message = message,
            Timestamp = ts,
            Category = category
        };
    }

    private async Task ProcessLoop()
    {
        var batch = new List<LogMessage>(_batchSize);
        var flushInterval = LoggerSettings.FileLoggerOptions.FlushPeriod;
        var timeoutMs = (int)Math.Min(flushInterval.TotalMilliseconds, int.MaxValue);

        try
        {
            while (!_cts.Token.IsCancellationRequested)
            {
                if (_queue.TryTake(out var item, timeoutMs, _cts.Token))
                {
                    batch.Add(item);
                }

                if (batch.Count >= _batchSize ||
                    (batch.Count > 0 && !_queue.TryTake(out _, 0)))
                {
                    await FlushBatchAsync(batch);
                }
            }
        }
        catch (OperationCanceledException)
        {
            if (batch.Count > 0)
            {
                await FlushBatchAsync(batch);
            }
        }
    }

    private async Task FlushBatchAsync(List<LogMessage> batch)
    {
        try
        {
            await WriteMessagesAsync(batch, _cts.Token).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            Console.WriteLine("An error occurred while processing log batches.", ex);
        }
        finally
        {
            batch.Clear();
        }
    }

    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        _disposed = true;

        _cts.Cancel();
        _queue.CompleteAdding();

        try { _worker.Wait(); } catch { }
        _cts.Dispose();
        _queue.Dispose();
    }
}
