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

namespace EonaCat.Logger.EonaCatCoreLogger.Internal;
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.

public abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
{
    private readonly int _batchSize;

    private readonly List<LogMessage> _currentBatch = new();
    private CancellationTokenSource _cancellationTokenSource;
    private LoggerSettings _loggerSettings;

    private ConcurrentQueue<LogMessage> _messageQueue;
    private Task _outputTask;

    protected BatchingLoggerProvider(IOptions<BatchingLoggerOptions> options)
    {
        var loggerOptions = options.Value;

        if (loggerOptions.FlushPeriod <= TimeSpan.Zero)
            throw new ArgumentOutOfRangeException(nameof(loggerOptions.FlushPeriod),
                $"{nameof(loggerOptions.FlushPeriod)} must be longer than zero.");

        if (options.Value is FileLoggerOptions fileLoggerOptions) UseLocalTime = fileLoggerOptions.UseLocalTime;
        _batchSize = loggerOptions.BatchSize;

        Start();
    }

    protected DateTimeOffset CurrentDateTimeOffset => UseLocalTime ? DateTimeOffset.Now : DateTimeOffset.UtcNow;
    protected DateTime CurrentDateTme => UseLocalTime ? DateTime.Now : DateTime.UtcNow;

    protected bool UseLocalTime { get; set; }

    protected LoggerSettings LoggerSettings
    {
        get
        {
            if (_loggerSettings != null) return _loggerSettings;

            _loggerSettings = new LoggerSettings();
            _loggerSettings.UseLocalTime = UseLocalTime;
            return _loggerSettings;
        }

        set => _loggerSettings = value;
    }

    public bool IsStarted { get; set; }

    public void Dispose()
    {
        while (!_messageQueue.IsEmpty) Task.Delay(10);

        StopAsync().GetAwaiter().GetResult();
        GC.SuppressFinalize(this);
    }

    public ILogger CreateLogger(string categoryName)
    {
        return new BatchingLogger(this, categoryName, LoggerSettings);
    }

    protected abstract Task WriteMessagesAsync(IEnumerable<LogMessage> messages, CancellationToken token);

    private async Task ProcessLogQueueAsync(object state)
    {
        var startupMessage = $"{DllInfo.ApplicationName} started.{Environment.NewLine}";
        startupMessage =
            LogHelper.FormatMessageWithHeader(LoggerSettings, ELogType.INFO, startupMessage, CurrentDateTme);
        AddMessage(DateTimeOffset.Now, startupMessage);

        while (!_cancellationTokenSource.IsCancellationRequested)
        {
            var limit = _batchSize <= 0 ? int.MaxValue : _batchSize;
            while (limit > 0 && _messageQueue.TryDequeue(out var message))
            {
                _currentBatch.Add(message);
                limit--;
            }

            if (_currentBatch.Count > 0)
                try
                {
                    await WriteMessagesAsync(_currentBatch, _cancellationTokenSource.Token).ConfigureAwait(false);
                    _currentBatch.Clear();
                }
                catch
                {
                    // ignored
                }

            Thread.Sleep(10);
        }
    }

    internal void AddMessage(DateTimeOffset timestamp, string message)
    {
        _messageQueue.Enqueue(new LogMessage { Message = message, Timestamp = timestamp });
    }

    private void Start()
    {
        IsStarted = true;
        _messageQueue = new ConcurrentQueue<LogMessage>();

        _cancellationTokenSource = new CancellationTokenSource();
        _outputTask = Task.Factory.StartNew(
            ProcessLogQueueAsync,
            null,
            TaskCreationOptions.LongRunning).ContinueWith(async x =>
        {
            var stopMessage = $"{DllInfo.ApplicationName} stopped.{Environment.NewLine}";
            stopMessage = LogHelper.FormatMessageWithHeader(LoggerSettings, ELogType.INFO, stopMessage, CurrentDateTme);
            await WriteMessagesAsync(
                    new List<LogMessage>(new List<LogMessage>
                        { new() { Message = stopMessage, Timestamp = CurrentDateTme } }),
                    _cancellationTokenSource.Token)
                .ConfigureAwait(false);
        });
    }

    private async Task StopAsync()
    {
        _cancellationTokenSource.Cancel();

        try
        {
            while (_outputTask.Status != TaskStatus.RanToCompletion && _outputTask.Status != TaskStatus.Canceled)
            {
                await _outputTask.ConfigureAwait(false);
                await Task.Delay(100);
            }
        }
        catch (TaskCanceledException)
        {
        }
        catch (AggregateException exception) when (exception.InnerExceptions.Count == 1 &&
                                                   exception.InnerExceptions[0] is TaskCanceledException)
        {
        }
    }

    ~BatchingLoggerProvider()
    {
        Dispose();
    }
}