using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Newtonsoft.Json;

namespace MusicBeePlugin.Clouseau.Logging
{
    /// <summary>
    /// Thread-safe ring buffer for storing log entries.
    /// </summary>
    public class LogBuffer : IEnumerable<LogEntry>, IDisposable
    {
        private readonly LogEntry[] _buffer;
        private readonly int _maxSize;
        private readonly ReaderWriterLockSlim _lock;

        private int _head;      // Next write position
        private int _count;     // Current number of items
        private bool _disposed;

        /// <summary>
        /// Gets the maximum capacity of the buffer.
        /// </summary>
        public int MaxSize => _maxSize;

        /// <summary>
        /// Gets the current number of entries in the buffer.
        /// </summary>
        public int Count
        {
            get
            {
                _lock.EnterReadLock();
                try
                {
                    return _count;
                }
                finally
                {
                    _lock.ExitReadLock();
                }
            }
        }

        /// <summary>
        /// Creates a new LogBuffer with the specified maximum size.
        /// </summary>
        /// <param name="maxSize">Maximum number of entries to store. Default is 10000.</param>
        public LogBuffer(int maxSize = 10000)
        {
            if (maxSize <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(maxSize), "Max size must be positive.");
            }

            _maxSize = maxSize;
            _buffer = new LogEntry[maxSize];
            _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
            _head = 0;
            _count = 0;
        }

        /// <summary>
        /// Adds a log entry to the buffer.
        /// </summary>
        /// <param name="entry">The log entry to add.</param>
        public void Add(LogEntry entry)
        {
            if (entry == null)
            {
                throw new ArgumentNullException(nameof(entry));
            }

            _lock.EnterWriteLock();
            try
            {
                _buffer[_head] = entry;
                _head = (_head + 1) % _maxSize;
                if (_count < _maxSize)
                {
                    _count++;
                }
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }

        /// <summary>
        /// Clears all entries from the buffer.
        /// </summary>
        public void Clear()
        {
            _lock.EnterWriteLock();
            try
            {
                Array.Clear(_buffer, 0, _buffer.Length);
                _head = 0;
                _count = 0;
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }

        /// <summary>
        /// Gets all entries in chronological order.
        /// </summary>
        /// <returns>All log entries in order.</returns>
        public IEnumerable<LogEntry> GetAll()
        {
            _lock.EnterReadLock();
            try
            {
                return GetEntriesInternal().ToList();
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Gets entries filtered by category.
        /// </summary>
        /// <param name="category">The category to filter by.</param>
        /// <returns>Filtered log entries.</returns>
        public IEnumerable<LogEntry> GetByCategory(string category)
        {
            if (string.IsNullOrEmpty(category))
            {
                return GetAll();
            }

            _lock.EnterReadLock();
            try
            {
                return GetEntriesInternal()
                    .Where(e => string.Equals(e.Category, category, StringComparison.OrdinalIgnoreCase))
                    .ToList();
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Gets entries filtered by multiple categories.
        /// </summary>
        /// <param name="categories">The categories to include.</param>
        /// <returns>Filtered log entries.</returns>
        public IEnumerable<LogEntry> GetByCategories(IEnumerable<string> categories)
        {
            if (categories == null || !categories.Any())
            {
                return GetAll();
            }

            var categorySet = new HashSet<string>(categories, StringComparer.OrdinalIgnoreCase);

            _lock.EnterReadLock();
            try
            {
                return GetEntriesInternal()
                    .Where(e => categorySet.Contains(e.Category))
                    .ToList();
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Gets entries filtered by log level.
        /// </summary>
        /// <param name="level">The minimum log level.</param>
        /// <returns>Filtered log entries.</returns>
        public IEnumerable<LogEntry> GetByLevel(string level)
        {
            if (string.IsNullOrEmpty(level))
            {
                return GetAll();
            }

            _lock.EnterReadLock();
            try
            {
                return GetEntriesInternal()
                    .Where(e => string.Equals(e.Level, level, StringComparison.OrdinalIgnoreCase))
                    .ToList();
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Gets entries filtered by minimum log level.
        /// </summary>
        /// <param name="minLevel">The minimum log level (Debug, Info, Warn, Error).</param>
        /// <returns>Filtered log entries.</returns>
        public IEnumerable<LogEntry> GetByMinLevel(string minLevel)
        {
            var minLevelValue = GetLevelValue(minLevel);

            _lock.EnterReadLock();
            try
            {
                return GetEntriesInternal()
                    .Where(e => GetLevelValue(e.Level) >= minLevelValue)
                    .ToList();
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Searches entries by message content (simple string match).
        /// </summary>
        /// <param name="searchText">The text to search for.</param>
        /// <param name="ignoreCase">Whether to ignore case. Default is true.</param>
        /// <returns>Matching log entries.</returns>
        public IEnumerable<LogEntry> Search(string searchText, bool ignoreCase = true)
        {
            if (string.IsNullOrEmpty(searchText))
            {
                return GetAll();
            }

            var comparison = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;

            _lock.EnterReadLock();
            try
            {
                return GetEntriesInternal()
                    .Where(e => MatchesSearch(e, searchText, comparison))
                    .ToList();
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Searches entries by regex pattern on message and context.
        /// </summary>
        /// <param name="pattern">The regex pattern to match.</param>
        /// <param name="options">Regex options. Default is IgnoreCase.</param>
        /// <returns>Matching log entries.</returns>
        public IEnumerable<LogEntry> SearchRegex(string pattern, RegexOptions options = RegexOptions.IgnoreCase)
        {
            if (string.IsNullOrEmpty(pattern))
            {
                return GetAll();
            }

            Regex regex;
            try
            {
                regex = new Regex(pattern, options);
            }
            catch (ArgumentException)
            {
                // Invalid regex pattern
                return Enumerable.Empty<LogEntry>();
            }

            _lock.EnterReadLock();
            try
            {
                return GetEntriesInternal()
                    .Where(e => MatchesRegex(e, regex))
                    .ToList();
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Gets entries within a specific time range.
        /// </summary>
        /// <param name="start">Start time (inclusive).</param>
        /// <param name="end">End time (inclusive).</param>
        /// <returns>Filtered log entries.</returns>
        public IEnumerable<LogEntry> GetByTimeRange(DateTime start, DateTime end)
        {
            _lock.EnterReadLock();
            try
            {
                return GetEntriesInternal()
                    .Where(e => e.Timestamp >= start && e.Timestamp <= end)
                    .ToList();
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Gets the most recent N entries.
        /// </summary>
        /// <param name="count">Number of entries to retrieve.</param>
        /// <returns>Most recent log entries.</returns>
        public IEnumerable<LogEntry> GetRecent(int count)
        {
            if (count <= 0)
            {
                return Enumerable.Empty<LogEntry>();
            }

            _lock.EnterReadLock();
            try
            {
                var entries = GetEntriesInternal().ToList();
                return entries.Skip(Math.Max(0, entries.Count - count)).ToList();
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Exports the log entries to a file.
        /// </summary>
        /// <param name="path">The file path to export to.</param>
        /// <param name="format">The export format (Text, Json, Csv).</param>
        /// <param name="filter">Optional filter function to select entries.</param>
        public void Export(string path, ExportFormat format, Func<LogEntry, bool> filter = null)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentNullException(nameof(path));
            }

            IEnumerable<LogEntry> entries;
            _lock.EnterReadLock();
            try
            {
                entries = GetEntriesInternal();
                if (filter != null)
                {
                    entries = entries.Where(filter);
                }
                entries = entries.ToList();
            }
            finally
            {
                _lock.ExitReadLock();
            }

            var directory = Path.GetDirectoryName(path);
            if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }

            switch (format)
            {
                case ExportFormat.Json:
                    ExportAsJson(path, entries);
                    break;
                case ExportFormat.Csv:
                    ExportAsCsv(path, entries);
                    break;
                case ExportFormat.Text:
                default:
                    ExportAsText(path, entries);
                    break;
            }
        }

        #region Private Methods

        private IEnumerable<LogEntry> GetEntriesInternal()
        {
            if (_count == 0)
            {
                yield break;
            }

            // Calculate the starting position (oldest entry)
            int start;
            if (_count < _maxSize)
            {
                start = 0;
            }
            else
            {
                start = _head; // When buffer is full, head points to oldest entry
            }

            for (int i = 0; i < _count; i++)
            {
                var index = (start + i) % _maxSize;
                if (_buffer[index] != null)
                {
                    yield return _buffer[index];
                }
            }
        }

        private bool MatchesSearch(LogEntry entry, string searchText, StringComparison comparison)
        {
            // Check message
            if (entry.Message != null && entry.Message.IndexOf(searchText, comparison) >= 0)
            {
                return true;
            }

            // Check event type
            if (entry.EventType != null && entry.EventType.IndexOf(searchText, comparison) >= 0)
            {
                return true;
            }

            // Check source file
            if (entry.SourceFile != null && entry.SourceFile.IndexOf(searchText, comparison) >= 0)
            {
                return true;
            }

            // Check context values
            if (entry.Context != null)
            {
                foreach (var value in entry.Context.Values)
                {
                    var stringValue = value?.ToString();
                    if (stringValue != null && stringValue.IndexOf(searchText, comparison) >= 0)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private bool MatchesRegex(LogEntry entry, Regex regex)
        {
            // Check message
            if (entry.Message != null && regex.IsMatch(entry.Message))
            {
                return true;
            }

            // Check event type
            if (entry.EventType != null && regex.IsMatch(entry.EventType))
            {
                return true;
            }

            // Check source file
            if (entry.SourceFile != null && regex.IsMatch(entry.SourceFile))
            {
                return true;
            }

            // Check context values
            if (entry.Context != null)
            {
                foreach (var value in entry.Context.Values)
                {
                    var stringValue = value?.ToString();
                    if (stringValue != null && regex.IsMatch(stringValue))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private int GetLevelValue(string level)
        {
            switch (level?.ToLowerInvariant())
            {
                case "debug":
                    return 0;
                case "info":
                    return 1;
                case "warn":
                case "warning":
                    return 2;
                case "error":
                    return 3;
                default:
                    return 1; // Default to Info
            }
        }

        private void ExportAsText(string path, IEnumerable<LogEntry> entries)
        {
            using (var writer = new StreamWriter(path, false, Encoding.UTF8))
            {
                writer.WriteLine("mb_clouseau Event Log Export");
                writer.WriteLine($"Exported: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
                writer.WriteLine(new string('=', 80));
                writer.WriteLine();

                foreach (var entry in entries)
                {
                    writer.WriteLine(entry.ToString());

                    if (entry.Context != null && entry.Context.Count > 0)
                    {
                        foreach (var kvp in entry.Context)
                        {
                            writer.WriteLine($"    {kvp.Key}: {kvp.Value}");
                        }
                    }

                    writer.WriteLine();
                }
            }
        }

        private void ExportAsJson(string path, IEnumerable<LogEntry> entries)
        {
            var json = JsonConvert.SerializeObject(entries, Formatting.Indented, new JsonSerializerSettings
            {
                DateFormatString = "yyyy-MM-ddTHH:mm:ss.fffK",
                NullValueHandling = NullValueHandling.Ignore
            });

            File.WriteAllText(path, json, Encoding.UTF8);
        }

        private void ExportAsCsv(string path, IEnumerable<LogEntry> entries)
        {
            using (var writer = new StreamWriter(path, false, Encoding.UTF8))
            {
                // Write header with Context as JSON column
                writer.WriteLine("Timestamp,SequenceNumber,Level,Category,EventType,EventTypeValue,SourceFile,Message,Context");

                foreach (var entry in entries)
                {
                    // Serialize context to JSON for CSV inclusion
                    var contextJson = entry.Context != null && entry.Context.Count > 0
                        ? JsonConvert.SerializeObject(entry.Context, Formatting.None)
                        : "";

                    writer.WriteLine(string.Join(",",
                        EscapeCsv(entry.Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff")),
                        entry.SequenceNumber,
                        EscapeCsv(entry.Level),
                        EscapeCsv(entry.Category),
                        EscapeCsv(entry.EventType),
                        entry.EventTypeValue,
                        EscapeCsv(entry.SourceFile),
                        EscapeCsv(entry.Message),
                        EscapeCsv(contextJson)
                    ));
                }
            }
        }

        private string EscapeCsv(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return "";
            }

            // If value contains comma, quote, or newline, wrap in quotes and escape quotes
            if (value.Contains(",") || value.Contains("\"") || value.Contains("\n") || value.Contains("\r"))
            {
                return "\"" + value.Replace("\"", "\"\"") + "\"";
            }

            return value;
        }

        #endregion

        #region IEnumerable

        public IEnumerator<LogEntry> GetEnumerator()
        {
            return GetAll().GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region IDisposable

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    _lock?.Dispose();
                }
                _disposed = true;
            }
        }

        #endregion
    }

    /// <summary>
    /// Export format options for log entries.
    /// </summary>
    public enum ExportFormat
    {
        /// <summary>
        /// Plain text format with human-readable layout.
        /// </summary>
        Text,

        /// <summary>
        /// JSON format for structured data.
        /// </summary>
        Json,

        /// <summary>
        /// CSV format for spreadsheet import.
        /// </summary>
        Csv
    }
}
