using EonaCat.LogStack.Core;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace EonaCat.LogStack.Batching;

// 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.

/// <summary>
/// Manages batch configuration and sizing strategies for optimized flow processing
/// </summary>
public class BatchConfig
{
    public int InitialBatchSize { get; set; } = 100;
    public int MaxBatchSize { get; set; } = 1000;
    public int MinBatchSize { get; set; } = 10;

    /// <summary>
    /// Maximum time to wait before flushing a partial batch (milliseconds)
    /// </summary>
    public int MaxBatchDelayMs { get; set; } = 1000;

    /// <summary>
    /// Threshold for memory usage before activating backpressure (bytes)
    /// </summary>
    public long BackpressureThresholdBytes { get; set; } = 100 * 1024 * 1024; // 100MB

    /// <summary>
    /// Enable adaptive batch sizing based on throughput and memory
    /// </summary>
    public bool EnableAdaptiveSizing { get; set; } = true;

    /// <summary>
    /// Enable backpressure handling
    /// </summary>
    public bool EnableBackpressure { get; set; } = true;
}

/// <summary>
/// Adaptive batch processor that dynamically adjusts batch sizes based on system conditions
/// </summary>
public sealed class AdaptiveBatchProcessor : IDisposable
{
    private readonly BatchConfig _config;
    private int _currentBatchSize;
    private long _currentMemoryUsage;
    private DateTime _lastSizeAdjustment = DateTime.UtcNow;
    private readonly object _lock = new object();
    private bool _isDisposed;

    public AdaptiveBatchProcessor(BatchConfig? config = null)
    {
        _config = config ?? new BatchConfig();
        _currentBatchSize = _config.InitialBatchSize;
    }

    /// <summary>
    /// Gets the optimal batch size based on current system conditions
    /// </summary>
    public int GetOptimalBatchSize()
    {
        if (!_config.EnableAdaptiveSizing)
        {
            return _currentBatchSize;
        }

        lock (_lock)
        {
            // Adjust batch size every 10 seconds based on memory pressure
            if ((DateTime.UtcNow - _lastSizeAdjustment).TotalSeconds < 10)
            {
                return _currentBatchSize;
            }

            _lastSizeAdjustment = DateTime.UtcNow;

            long availableMemory = GC.GetTotalMemory(false);

            if (availableMemory > _config.BackpressureThresholdBytes)
            {
                // Reduce batch size under memory pressure
                _currentBatchSize = Math.Max(
                    _config.MinBatchSize,
                    (int)(_currentBatchSize * 0.9));
            }
            else if (availableMemory < _config.BackpressureThresholdBytes / 2)
            {
                // Increase batch size when memory is available
                _currentBatchSize = Math.Min(
                    _config.MaxBatchSize,
                    (int)(_currentBatchSize * 1.1));
            }

            return _currentBatchSize;
        }
    }

    /// <summary>
    /// Records memory usage of processed events
    /// </summary>
    public void RecordMemoryUsage(long bytes)
    {
        lock (_lock)
        {
            _currentMemoryUsage = bytes;
        }
    }

    /// <summary>
    /// Checks if backpressure should be applied
    /// </summary>
    public bool ShouldApplyBackpressure()
    {
        if (!_config.EnableBackpressure)
        {
            return false;
        }

        lock (_lock)
        {
            return _currentMemoryUsage > _config.BackpressureThresholdBytes;
        }
    }

    /// <summary>
    /// Gets the current memory usage
    /// </summary>
    public long GetMemoryUsage()
    {
        lock (_lock)
        {
            return _currentMemoryUsage;
        }
    }

    /// <summary>
    /// Resets memory tracking
    /// </summary>
    public void ResetMemoryUsage()
    {
        lock (_lock)
        {
            _currentMemoryUsage = 0;
        }
    }

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

        _isDisposed = true;
    }
}

/// <summary>
/// Backpressure handler that controls flow based on system resources
/// </summary>
public sealed class BackpressureHandler : IDisposable
{
    private readonly long _thresholdBytes;
    private readonly int _maxWaitMs;
    private bool _isDisposed;

    public BackpressureHandler(long thresholdBytes = 100 * 1024 * 1024, int maxWaitMs = 5000)
    {
        _thresholdBytes = thresholdBytes;
        _maxWaitMs = maxWaitMs;
    }

    /// <summary>
    /// Applies backpressure by waiting if memory usage exceeds threshold
    /// </summary>
    public async Task ApplyBackpressureAsync(CancellationToken cancellationToken = default)
    {
        if (_isDisposed)
        {
            return;
        }

        long memoryUsage = GC.GetTotalMemory(false);

        if (memoryUsage <= _thresholdBytes)
        {
            return;
        }

        // Wait for memory to be released
        var startTime = DateTime.UtcNow;
        while (GC.GetTotalMemory(false) > _thresholdBytes)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                break;
            }

            if ((DateTime.UtcNow - startTime).TotalMilliseconds > _maxWaitMs)
            {
                break;
            }

            // Collect garbage and wait
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized, false);
            await Task.Delay(100, cancellationToken).ConfigureAwait(false);
        }
    }

    /// <summary>
    /// Gets current memory pressure ratio (0.0 = no pressure, 1.0+ = critical)
    /// </summary>
    public double GetMemoryPressure()
    {
        long memoryUsage = GC.GetTotalMemory(false);
        return (double)memoryUsage / _thresholdBytes;
    }

    /// <summary>
    /// Checks if backpressure is currently active
    /// </summary>
    public bool IsActive => GetMemoryPressure() > 0.8;

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

        _isDisposed = true;
    }
}

/// <summary>
/// Generic batch accumulator for collecting items before processing
/// </summary>
public sealed class BatchAccumulator<T> : IDisposable
{
    private readonly List<T> _items;
    private readonly int _maxSize;
    private readonly int _maxWaitMs;
    private DateTime _lastFlush;
    private readonly object _lock = new object();
    private bool _isDisposed;

    public BatchAccumulator(int maxSize = 100, int maxWaitMs = 1000)
    {
        _maxSize = maxSize;
        _maxWaitMs = maxWaitMs;
        _items = new List<T>(maxSize);
        _lastFlush = DateTime.UtcNow;
    }

    /// <summary>
    /// Adds an item to the batch
    /// </summary>
    public bool TryAdd(T item)
    {
        lock (_lock)
        {
            if (_isDisposed)
            {
                return false;
            }

            _items.Add(item);
            return true;
        }
    }

    /// <summary>
    /// Checks if the batch should be flushed
    /// </summary>
    public bool ShouldFlush()
    {
        lock (_lock)
        {
            if (_items.Count == 0)
            {
                return false;
            }

            // Flush if full
            if (_items.Count >= _maxSize)
            {
                return true;
            }

            // Flush if timeout exceeded
            var elapsed = (DateTime.UtcNow - _lastFlush).TotalMilliseconds;
            return elapsed > _maxWaitMs;
        }
    }

    /// <summary>
    /// Gets the current batch and resets
    /// </summary>
    public T[] GetAndReset()
    {
        lock (_lock)
        {
            if (_items.Count == 0)
            {
                return Array.Empty<T>();
            }

            var batch = _items.ToArray();
            _items.Clear();
            _lastFlush = DateTime.UtcNow;
            return batch;
        }
    }

    /// <summary>
    /// Gets the count of items in the current batch
    /// </summary>
    public int Count
    {
        get
        {
            lock (_lock)
            {
                return _items.Count;
            }
        }
    }

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

        lock (_lock)
        {
            _isDisposed = true;
            _items.Clear();
        }
    }
}
