using EonaCat.LogStack.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace EonaCat.LogStack.Flows;

// 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>
/// In-memory flow with circular buffer for diagnostics and testing.
/// Designed for high-speed logging with bounded memory usage.
/// </summary>
public sealed class MemoryFlow : FlowBase
{
    private readonly LogEvent[] _buffer;
    private readonly int _capacity;
    private int _head;
    private int _tail;
    private int _count;
    private readonly object _lock = new();

    public MemoryFlow(
        int capacity = 10000,
        LogLevel minimumLevel = LogLevel.Trace)
        : base("Memory", minimumLevel)
    {
        if (capacity <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(capacity));
        }

        _capacity = capacity;
        _buffer = new LogEvent[capacity];
        _head = 0;
        _tail = 0;
        _count = 0;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public override Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
    {
        if (!IsEnabled || !IsLogLevelEnabled(logEvent))
        {
            return Task.FromResult(WriteResult.LevelFiltered);
        }

        lock (_lock)
        {
            _buffer[_tail] = logEvent;
            _tail = (_tail + 1) % _capacity;

            if (_count == _capacity)
            {
                // Buffer is full, overwrite oldest
                _head = (_head + 1) % _capacity;
                Interlocked.Increment(ref DroppedCount);
            }
            else
            {
                _count++;
            }
        }

        Interlocked.Increment(ref BlastedCount);
        return Task.FromResult(WriteResult.Success);
    }

    public override Task FlushAsync(CancellationToken cancellationToken = default)
    {
        // No-op for memory flow
        return Task.CompletedTask;
    }

    /// <summary>
    /// Gets all log events currently in the buffer
    /// </summary>
    public LogEvent[] GetEvents()
    {
        lock (_lock)
        {
            var events = new LogEvent[_count];

            for (int i = 0; i < _count; i++)
            {
                var index = (_head + i) % _capacity;
                events[i] = _buffer[index];
            }

            return events;
        }
    }

    /// <summary>
    /// Gets events matching the specified level
    /// </summary>
    public LogEvent[] GetEvents(LogLevel level)
    {
        lock (_lock)
        {
            var matching = new List<LogEvent>(_count);

            for (int i = 0; i < _count; i++)
            {
                var index = (_head + i) % _capacity;
                if (_buffer[index].Level == level)
                {
                    matching.Add(_buffer[index]);
                }
            }

            return matching.ToArray();
        }
    }

    /// <summary>
    /// Gets the most recent N events
    /// </summary>
    public LogEvent[] GetRecentEvents(int count)
    {
        lock (_lock)
        {
            var actualCount = Math.Min(count, _count);
            var events = new LogEvent[actualCount];

            for (int i = 0; i < actualCount; i++)
            {
                var index = (_tail - actualCount + i + _capacity) % _capacity;
                events[i] = _buffer[index];
            }

            return events;
        }
    }

    /// <summary>
    /// Clears all events from the buffer
    /// </summary>
    public void Clear()
    {
        lock (_lock)
        {
            Array.Clear(_buffer, 0, _buffer.Length);
            _head = 0;
            _tail = 0;
            _count = 0;
        }
    }

    /// <summary>
    /// Gets the current count of events in the buffer
    /// </summary>
    public int Count
    {
        get
        {
            lock (_lock)
            {
                return _count;
            }
        }
    }

    /// <summary>
    /// Gets whether the buffer is full
    /// </summary>
    public bool IsFull
    {
        get
        {
            lock (_lock)
            {
                return _count == _capacity;
            }
        }
    }

    // LINQ-style Query API
    /// <summary>
    /// Returns all buffered events matching an arbitrary predicate.
    /// The buffer snapshot is taken under lock; the predicate is evaluated outside.
    /// </summary>
    public IReadOnlyList<LogEvent> Query(Func<LogEvent, bool> predicate)
    {
        if (predicate == null)
        {
            throw new ArgumentNullException(nameof(predicate));
        }

        return GetEvents().Where(predicate).ToList();
    }

    /// <summary>Returns all events at or above the given level.</summary>
    public IReadOnlyList<LogEvent> QueryByLevel(LogLevel minimumLevel)
        => Query(e => e.Level >= minimumLevel);

    /// <summary>Returns all events whose Category equals <paramref name="category"/> (case-insensitive).</summary>
    public IReadOnlyList<LogEvent> QueryByCategory(string category)
    {
        if (string.IsNullOrEmpty(category))
        {
            throw new ArgumentNullException(nameof(category));
        }

        return Query(e => string.Equals(e.Category, category, StringComparison.OrdinalIgnoreCase));
    }

    /// <summary>Returns all events that carry an exception of (or derived from) <typeparamref name="TException"/>.</summary>
    public IReadOnlyList<LogEvent> QueryByException<TException>() where TException : Exception
        => Query(e => e.HasException && e.Exception is TException);

    /// <summary>Returns all events whose message contains the given substring (case-insensitive).</summary>
    public IReadOnlyList<LogEvent> QueryByMessage(string substring)
    {
        if (substring == null)
        {
            throw new ArgumentNullException(nameof(substring));
        }

        return Query(e => e.Message.Span.IndexOf(substring.AsSpan(), StringComparison.OrdinalIgnoreCase) >= 0);
    }

    /// <summary>Returns all events that carry a property with the given key.</summary>
    public IReadOnlyList<LogEvent> QueryByProperty(string key)
    {
        if (key == null)
        {
            throw new ArgumentNullException(nameof(key));
        }

        return Query(e => e.HasProperties && e.Properties.ContainsKey(key));
    }

    /// <summary>Returns all events that carry a property with a specific key and value.</summary>
    public IReadOnlyList<LogEvent> QueryByProperty(string key, object value)
    {
        if (key == null)
        {
            throw new ArgumentNullException(nameof(key));
        }

        return Query(e => e.HasProperties
                       && e.Properties.TryGetValue(key, out var v)
                       && Equals(v, value));
    }

    /// <summary>Returns the <paramref name="n"/> most recent events (newest last).</summary>
    public IReadOnlyList<LogEvent> GetLatest(int n) => GetRecentEvents(n);

    /// <summary>
    /// Returns aggregate statistics for the current buffer snapshot.
    /// </summary>
    public MemoryFlowStats GetStats()
    {
        var events = GetEvents();
        var byLevel = new Dictionary<LogLevel, int>();
        foreach (var e in events)
        {
            byLevel.TryGetValue(e.Level, out var cnt);
            byLevel[e.Level] = cnt + 1;
        }

        return new MemoryFlowStats
        {
            TotalEvents     = events.Length,
            Capacity        = _capacity,
            IsFull          = IsFull,
            ByLevel         = byLevel,
            WithExceptions  = events.Count(e => e.HasException),
            OldestTimestamp = events.Length > 0 ? LogEvent.GetDateTime(events[0].Timestamp)              : (DateTime?)null,
            NewestTimestamp = events.Length > 0 ? LogEvent.GetDateTime(events[events.Length - 1].Timestamp) : (DateTime?)null,
        };
    }
}

/// <summary>Aggregate statistics returned by <see cref="MemoryFlow.GetStats"/>.</summary>
public sealed class MemoryFlowStats
{
    public int TotalEvents { get; set; }
    public int Capacity { get; set; }
    public bool IsFull { get; set; }
    public Dictionary<LogLevel, int> ByLevel { get; set; } = new Dictionary<LogLevel, int>();
    public int WithExceptions { get; set; }
    public DateTime? OldestTimestamp { get; set; }
    public DateTime? NewestTimestamp { get; set; }
}
