using EonaCat.LogStack.Core;
using EonaCat.LogStack.EonaCatLogStackCore;
using EonaCat.LogStack.EonaCatLogStackCore.Policies;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
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>
    /// Writes log events to AES-256-CBC encrypted, append-only binary files.
    ///
    /// File layout:
    ///   [4 bytes magic "EONA"] [32 bytes salt] [16 bytes IV]
    ///   repeated: [4 bytes LE block-length] [N bytes ciphertext]
    ///
    /// Key derivation: PBKDF2-HMACSHA1 with 100 000 iterations.
    /// Each individual line is encrypted independently (ECB-safe CBC block) so the
    /// file can be read entry-by-entry via <see cref="DecryptToFile"/>.
    ///
    /// </summary>
    public sealed class EncryptedFileFlow : FlowBase
    {
        public event EventHandler<string> OnDirectoryException;
        private static readonly byte[] Magic = new byte[] { 0x45, 0x4F, 0x4E, 0x41 }; // "EONA"
        private const int SaltSize = 32;
        private const int IvSize = 16;
        private const int KeySize = 32;   // AES-256
        private const int Pbkdf2Iter = 100000;

        private const int QueueCapacity = 8192;

        private readonly BlockingCollection<QueueEntry> _queue;
        private readonly CancellationTokenSource _cts = new CancellationTokenSource();
        private readonly Thread _writerThread;
        private readonly Thread _flushThread;
        private readonly Thread _retentionThread;
        private readonly Stopwatch _uptime = Stopwatch.StartNew();

        private struct QueueEntry
        {
            public string Line;
            public LogLevel Level;
        }

        private readonly string _directory;
        private readonly string _filePrefix;
        private readonly string _password;
        private readonly long _maxFileSize;
        private readonly long _maxDirectorySize;
        private readonly FileRetentionPolicy _retention;
        private readonly int _flushIntervalMs;
        private readonly int _batchSize;
        private readonly TimestampMode _timestampMode;
        private readonly BackpressureStrategy _backpressure;
        private readonly FileOutputFormat _outputFormat;
        private readonly CompressionFormat _compressionFormat;
        private readonly string _template;
        private readonly bool _useCategoryRouting;
        private readonly HashSet<LogLevel> _logLevelsForSeparateFiles;
        private readonly long _maxMemoryBytes;

        private readonly object _lock = new object();
        private FileStream _currentStream;
        private ICryptoTransform _encryptor;
        private string _currentPath;
        private long _currentSize;
        private DateTime _currentDate;

        private long _totalWritten;
        private long _totalRotations;
        private long _totalBytesWritten;
        private long _currentMemoryBytes;

        public bool IgnoreConsoleErrors { get; set; }

        private volatile SamplingPolicy _samplingPolicy;
        private volatile Action<LogEvent> _onDrop;
        private volatile Action<string> _onRotate;
        private List<Action<LogEvent, string>> _secondaryWriters;
        private readonly object _secondaryWritersLock = new object();

        private readonly List<Func<LogEvent, bool>> _filters = new List<Func<LogEvent, bool>>();
        private readonly object _filtersLock = new object();

        private readonly ConcurrentDictionary<string, long> _deduplicationCache
            = new ConcurrentDictionary<string, long>(StringComparer.Ordinal);
        private TimeSpan _deduplicationWindow = TimeSpan.Zero;
        private volatile bool _deduplicationEnabled;

        private string _dateFormat = "yyyyMMdd";
        private volatile Exception _lastError;
        private long _lastErrorTimestamp;
        private long _totalErrors;

        private readonly List<KeyValuePair<string, Func<LogEvent, object>>> _enrichers
            = new List<KeyValuePair<string, Func<LogEvent, object>>>();

        // Rate limiting
        private int _maxEventsPerSecond;
        private bool _rateLimitEnabled => _maxEventsPerSecond > 0;
        private long _rateLimitWindowStart;
        private int _rateLimitCounter;
        private readonly object _rateLimitLock = new object();

        // Auto-flush on error
        private volatile bool _autoFlushOnError;

        // Scoped properties (AsyncLocal for ambient context)
        private static readonly AsyncLocal<Dictionary<string, object>> _scopeProperties
            = new AsyncLocal<Dictionary<string, object>>();

        private static readonly Dictionary<LogLevel, string> LevelStrings =
            new Dictionary<LogLevel, string>
            {
                { LogLevel.Trace,   "TRACE" },
                { LogLevel.Debug,   "DEBUG" },
                { LogLevel.Information,    "INFO"  },
                { LogLevel.Warning, "WARN"  },
                { LogLevel.Error,   "ERROR" },
                { LogLevel.Critical,   "CRITICAL" },
            };

        private static readonly string CachedMachineName = Environment.MachineName;
        private static readonly int CachedPid = Process.GetCurrentProcess().Id;

        private List<Action<LogEvent, StringBuilder>> _compiledTemplate;
        private readonly Dictionary<string, Action<LogEvent, StringBuilder>> _customTokens
            = new Dictionary<string, Action<LogEvent, StringBuilder>>(StringComparer.OrdinalIgnoreCase);

        private int _correlationSeed;

        public EncryptedFileFlow(
            string directory,
            string password,
            string filePrefix = "encrypted_log",
            long maxFileSize = 50L * 1024 * 1024,
            long maxDirectorySize = 2L * 1024 * 1024 * 1024,
            FileRetentionPolicy retention = null,
            int flushIntervalMs = 3000,
            int batchSize = 1,
            LogLevel minimumLevel = LogLevel.Trace,
            bool useCategoryRouting = false,
            LogLevel[] logLevelsForSeparateFiles = null,
            TimestampMode tsMode = TimestampMode.Utc,
            BackpressureStrategy backpressure = BackpressureStrategy.DropOldest,
            FileOutputFormat outputFormat = FileOutputFormat.Text,
            CompressionFormat compression = CompressionFormat.GZip,
            string template = "[{ts}] [Host: {host}] [Category: {category}] [Thread: {thread}] [{logtype}] {message}{props}",
            long maxMemoryBytes = 20 * 1024 * 1024)
            : base("EncryptedFile:" + directory, minimumLevel)
        {
            if (directory == null)
            {
                throw new ArgumentNullException("directory");
            }

            if (password == null)
            {
                throw new ArgumentNullException("password");
            }

            if (filePrefix == null)
            {
                throw new ArgumentNullException("filePrefix");
            }

            if (template == null)
            {
                throw new ArgumentNullException("template");
            }

            CheckForProcessTermination();

            _directory = directory;
            _password = password;
            _filePrefix = filePrefix;
            _maxFileSize = maxFileSize;
            _maxDirectorySize = maxDirectorySize > 0 ? maxDirectorySize : 10L * maxFileSize;
            _retention = retention ?? new FileRetentionPolicy();
            _flushIntervalMs = flushIntervalMs;
            _batchSize = batchSize <= 0 ? 1 : batchSize;
            _timestampMode = tsMode;
            _backpressure = backpressure;
            _outputFormat = outputFormat;
            _compressionFormat = compression;
            _template = template;
            _useCategoryRouting = useCategoryRouting;
            _maxMemoryBytes = maxMemoryBytes;

            _logLevelsForSeparateFiles = logLevelsForSeparateFiles != null
                ? new HashSet<LogLevel>(logLevelsForSeparateFiles)
                : new HashSet<LogLevel>();

            CompileTemplate(template);

            // Resolve relative path
            if (_directory.StartsWith("./", StringComparison.Ordinal))
            {
                _directory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _directory.Substring(2));
            }

            try
            {
                Directory.CreateDirectory(_directory);
            }
            catch
            {
                try
                {
                    var processId = Process.GetCurrentProcess().Id;
                    var newDirectory = Path.Combine(Path.GetTempPath(), "EonaCat.LogStack", processId.ToString());
                    Directory.CreateDirectory(newDirectory);
                    OnDirectoryException?.Invoke(this, $"Could not create directory: '{_directory}', using directory '{newDirectory}' instead");
                    _directory = newDirectory;
                }
                catch
                {
                    var newDirectory = Path.GetTempPath();
                    OnDirectoryException?.Invoke(this, $"Could not create directory: '{_directory}', using directory '{newDirectory}' instead");
                    _directory = newDirectory;

                    // Last resort: disable file output by pointing to a safe-ish temp path.
                    // The writer thread still runs and swallows failures.
                }
            }

            _queue = new BlockingCollection<QueueEntry>(new ConcurrentQueue<QueueEntry>(), QueueCapacity);

            _writerThread = new Thread(WriterLoop)
            {
                IsBackground = true,
                Name = "EncryptedFileFlow.Writer[" + filePrefix + "]",
                Priority = ThreadPriority.AboveNormal
            };
            _writerThread.Start();

            _flushThread = new Thread(FlushLoop)
            {
                IsBackground = true,
                Name = "EncryptedFileFlow.Flush[" + filePrefix + "]",
                Priority = ThreadPriority.BelowNormal
            };
            _flushThread.Start();

            _retentionThread = new Thread(RetentionLoop)
            {
                IsBackground = true,
                Name = "EncryptedFileFlow.Retention[" + filePrefix + "]",
                Priority = ThreadPriority.BelowNormal
            };
            _retentionThread.Start();
        }

        private void CheckForProcessTermination()
        {
            AppDomain.CurrentDomain.ProcessExit += (_, __) =>
            {
                try
                {
                    DisposeAsync().AsTask().GetAwaiter().GetResult();
                }
                catch
                {
                    // Do nothing
                }
            };

            Console.CancelKeyPress += (_, e) =>
            {
                try
                {
                    DisposeAsync().AsTask().GetAwaiter().GetResult();
                }
                catch
                {
                    // Do nothing
                }
            };
        }

        /// <summary>Add an ambient property enricher applied to every event.</summary>
        public EncryptedFileFlow EnrichWith(string key, Func<LogEvent, object> valueFactory)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }

            if (valueFactory == null)
            {
                throw new ArgumentNullException("valueFactory");
            }

            _enrichers.Add(new KeyValuePair<string, Func<LogEvent, object>>(key, valueFactory));
            return this;
        }

        /// <summary>Add a static ambient property.</summary>
        public EncryptedFileFlow EnrichWith(string key, object value)
        {
            return EnrichWith(key, _ => value);
        }

        /// <summary>Configure sampling: only log 1 in <paramref name="rate"/> events
        /// that match <paramref name="predicate"/>.</summary>
        public EncryptedFileFlow WithSampling(int rate, Func<LogEvent, bool> predicate = null)
        {
            _samplingPolicy = new SamplingPolicy { Rate = rate, Predicate = predicate };
            return this;
        }

        /// <summary>Callback invoked when an event is dropped due to backpressure.</summary>
        public EncryptedFileFlow OnEventDropped(Action<LogEvent> callback)
        {
            _onDrop = callback;
            return this;
        }

        /// <summary>Callback invoked with the archived path after each file rotation.</summary>
        public EncryptedFileFlow OnFileRotated(Action<string> callback)
        {
            _onRotate = callback;
            return this;
        }

        /// <summary>Fan-out: also invoke <paramref name="writer"/> for every formatted log line.</summary>
        public EncryptedFileFlow AddSecondaryWriter(Action<LogEvent, string> writer)
        {
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }

            lock (_secondaryWritersLock)
            {
                if (_secondaryWriters == null)
                {
                    _secondaryWriters = new List<Action<LogEvent, string>>();
                }

                _secondaryWriters.Add(writer);
            }
            return this;
        }

        /// <summary>Change the minimum log level at runtime (thread-safe).</summary>
        public void SetMinimumLevel(LogLevel level)
        {
            MinimumLevel = level;
        }

        /// <summary>Register a custom template token (e.g. {mytoken}).</summary>
        public EncryptedFileFlow RegisterToken(string name, Action<LogEvent, StringBuilder> formatter)
        {
            if (formatter == null)
            {
                throw new ArgumentNullException("formatter");
            }

            _customTokens[name] = formatter;
            return this;
        }

        /// <summary>Returns the current estimated memory usage of the queue in bytes.</summary>
        public long GetMemoryPressureBytes()
        {
            return Interlocked.Read(ref _currentMemoryBytes);
        }

        /// <summary>Add a custom filter predicate. Events are logged only if ALL filters return true.</summary>
        public EncryptedFileFlow WithFilter(Func<LogEvent, bool> predicate)
        {
            if (predicate == null)
            {
                throw new ArgumentNullException("predicate");
            }

            lock (_filtersLock)
            {
                _filters.Add(predicate);
            }
            return this;
        }

        /// <summary>Enable deduplication: suppress identical messages within the given time window.</summary>
        public EncryptedFileFlow WithDeduplication(TimeSpan window)
        {
            if (window <= TimeSpan.Zero)
            {
                throw new ArgumentOutOfRangeException("window", "Deduplication window must be positive.");
            }

            _deduplicationWindow = window;
            _deduplicationEnabled = true;
            return this;
        }

        /// <summary>Configure a custom date format for log file names (default: yyyyMMdd).</summary>
        public EncryptedFileFlow WithDateFormat(string dateFormat)
        {
            if (string.IsNullOrWhiteSpace(dateFormat))
            {
                throw new ArgumentNullException("dateFormat");
            }

            _dateFormat = dateFormat;
            return this;
        }

        /// <summary>Limit the flow to a maximum number of events per second. Events exceeding the limit are dropped.</summary>
        public EncryptedFileFlow WithRateLimit(int maxEventsPerSecond)
        {
            _maxEventsPerSecond = maxEventsPerSecond;
            return this;
        }

        /// <summary>When enabled, the file stream is flushed immediately after writing Error or Critical level events.</summary>
        public EncryptedFileFlow WithAutoFlushOnError(bool enabled = true)
        {
            _autoFlushOnError = enabled;
            return this;
        }

        /// <summary>Push scoped properties that will be included in all log events written on the current async context.</summary>
        public IDisposable BeginScope(params KeyValuePair<string, object>[] properties)
        {
            var previous = _scopeProperties.Value;
            var merged = previous != null
                ? new Dictionary<string, object>(previous)
                : new Dictionary<string, object>();

            foreach (var kv in properties)
            {
                merged[kv.Key] = kv.Value;
            }

            _scopeProperties.Value = merged;
            return new ScopeDisposable(previous);
        }

        /// <summary>Push a single scoped property.</summary>
        public IDisposable BeginScope(string key, object value)
        {
            return BeginScope(new KeyValuePair<string, object>(key, value));
        }

        /// <summary>Returns the current queue depth (number of pending events).</summary>
        public int GetQueueDepth()
        {
            return _queue.Count;
        }

        /// <summary>Generates a fingerprint hash for an exception to assist with grouping.</summary>
        public static string GetExceptionFingerprint(Exception ex)
        {
            if (ex == null)
            {
                return null;
            }

            string source = string.Concat(
                ex.GetType().FullName, "|",
                ex.TargetSite?.Name ?? string.Empty, "|",
                ex.StackTrace != null && ex.StackTrace.Length > 0
                    ? ex.StackTrace.Substring(0, Math.Min(200, ex.StackTrace.Length))
                    : string.Empty);

            // Simple FNV-1a hash
            unchecked
            {
                uint hash = 2166136261;
                foreach (char c in source)
                {
                    hash ^= c;
                    hash *= 16777619;
                }
                return hash.ToString("x8");
            }
        }

        private sealed class ScopeDisposable : IDisposable
        {
            private readonly Dictionary<string, object> _previous;

            public ScopeDisposable(Dictionary<string, object> previous)
            {
                _previous = previous;
            }

            public void Dispose()
            {
                _scopeProperties.Value = _previous;
            }
        }

        /// <summary>Returns true if the flow is healthy (no recent errors and writer thread alive).</summary>
        public bool IsHealthy()
        {
            if (!IsEnabled)
            {
                return false;
            }

            if (!_writerThread.IsAlive)
            {
                return false;
            }

            long lastErr = Interlocked.Read(ref _lastErrorTimestamp);
            if (lastErr > 0)
            {
                TimeSpan since = TimeSpan.FromTicks(DateTime.UtcNow.Ticks - lastErr);
                if (since < TimeSpan.FromMinutes(1))
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>Returns the last error encountered by the writer, or null if none.</summary>
        public Exception GetLastError()
        {
            return _lastError;
        }

        /// <summary>Returns the total number of write errors encountered.</summary>
        public long GetTotalErrors()
        {
            return Interlocked.Read(ref _totalErrors);
        }

        /// <summary>
        /// Decrypts an .eona file produced by this flow to a plain-text file.
        /// </summary>
        public static bool DecryptToFile(string encryptedPath, string outputPath, string password)
        {
            if (encryptedPath == null)
            {
                throw new ArgumentNullException("encryptedPath");
            }

            if (outputPath == null)
            {
                throw new ArgumentNullException("outputPath");
            }

            if (password == null)
            {
                throw new ArgumentNullException("password");
            }

            try
            {
                using (FileStream source = File.OpenRead(encryptedPath))
                {
                    byte[] magic = new byte[4];
                    ReadExact(source, magic, 4);
                    for (int i = 0; i < 4; i++)
                    {
                        if (magic[i] != Magic[i])
                        {
                            throw new InvalidDataException("Not a valid EONA encrypted log file.");
                        }
                    }

                    byte[] salt = new byte[SaltSize];
                    byte[] iv = new byte[IvSize];
                    ReadExact(source, salt, SaltSize);
                    ReadExact(source, iv, IvSize);

                    byte[] key = DeriveKey(password, salt);

                    using (Aes aes = Aes.Create())
                    {
                        aes.KeySize = 256;
                        aes.Mode = CipherMode.CBC;
                        aes.Padding = PaddingMode.PKCS7;
                        aes.Key = key;
                        aes.IV = iv;

                        using (ICryptoTransform dec = aes.CreateDecryptor())
                        using (StreamWriter out_ = new StreamWriter(outputPath, false, Encoding.UTF8))
                        {
                            byte[] buffer = new byte[4];
                            while (source.Position < source.Length)
                            {
                                int read = source.Read(buffer, 0, 4);
                                if (read < 4)
                                {
                                    break;
                                }

                                int blockLength = BitConverter.ToInt32(buffer, 0);
                                if (blockLength <= 0 || blockLength > 16 * 1024 * 1024)
                                {
                                    throw new InvalidDataException("Corrupt block at offset " + (source.Position - 4));
                                }

                                byte[] cipher = new byte[blockLength];
                                ReadExact(source, cipher, blockLength);
                                byte[] plain = dec.TransformFinalBlock(cipher, 0, cipher.Length);
                                out_.WriteLine(Encoding.UTF8.GetString(plain));
                                return true;
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine($"Exception during decryption => {e.Message}");
            }
            return false;
        }

        public LogStats GetStats()
        {
            long written = Interlocked.Read(ref BlastedCount);
            long dropped = Interlocked.Read(ref DroppedCount);
            long bytes = Interlocked.Read(ref _totalBytesWritten);
            long rots = Interlocked.Read(ref _totalRotations);
            double elapsed = _uptime.Elapsed.TotalSeconds;
            double wps = elapsed > 0 ? written / elapsed : 0;
            return new LogStats(written, dropped, rots, bytes, wps);
        }

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

            SamplingPolicy sp = _samplingPolicy;
            if (sp != null && !sp.ShouldLog(logEvent))
            {
                return Task.FromResult(WriteResult.LevelFiltered);
            }

            if (!PassesFilters(logEvent))
            {
                return Task.FromResult(WriteResult.LevelFiltered);
            }

            if (_deduplicationEnabled && IsDuplicate(logEvent))
            {
                return Task.FromResult(WriteResult.LevelFiltered);
            }

            if (_rateLimitEnabled && !TryPassRateLimit())
            {
                Interlocked.Increment(ref DroppedCount);
                Action<LogEvent> drop = _onDrop;
                if (drop != null)
                {
                    drop(logEvent);
                }

                return Task.FromResult(WriteResult.Dropped);
            }

            return Task.FromResult(TryEnqueue(logEvent));
        }

        public override Task<WriteResult> BlastBatchAsync(
            ReadOnlyMemory<LogEvent> logEvents,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            if (!IsEnabled)
            {
                return Task.FromResult(WriteResult.FlowDisabled);
            }

            WriteResult result = WriteResult.Success;
            SamplingPolicy sp = _samplingPolicy;
            ReadOnlySpan<LogEvent> span = logEvents.Span;

            for (int i = 0; i < span.Length; i++)
            {
                LogEvent e = span[i];
                if (e.Level < MinimumLevel)
                {
                    continue;
                }

                if (sp != null && !sp.ShouldLog(e))
                {
                    continue;
                }

                if (!PassesFilters(e))
                {
                    continue;
                }

                if (_deduplicationEnabled && IsDuplicate(e))
                {
                    continue;
                }

                if (_rateLimitEnabled && !TryPassRateLimit())
                {
                    Interlocked.Increment(ref DroppedCount);
                    Action<LogEvent> drop = _onDrop;
                    if (drop != null)
                    {
                        drop(e);
                    }

                    result = WriteResult.Dropped;
                    continue;
                }

                if (TryEnqueue(e) == WriteResult.Dropped)
                {
                    result = WriteResult.Dropped;
                }
            }

            return Task.FromResult(result);
        }

        public override Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            lock (_lock)
            {
                if (_currentStream != null)
                {
                    _currentStream.Flush(true);
                }
            }
            return Task.FromResult(0);
        }

        public override async ValueTask DisposeAsync()
        {
            if (!IsEnabled)
            {
                return;
            }

            IsEnabled = false;
            _queue.CompleteAdding();
            _cts.Cancel();
            _writerThread.Join(TimeSpan.FromSeconds(5));
            _flushThread.Join(TimeSpan.FromSeconds(2));
            _retentionThread.Join(TimeSpan.FromSeconds(2));
            lock (_lock) { CloseCurrentFile(); }
            _cts.Dispose();
            _queue.Dispose();
            await base.DisposeAsync().ConfigureAwait(false);
        }

        private void WriterLoop()
        {
            try
            {
                while (!_queue.IsCompleted)
                {
                    QueueEntry entry;
                    try { entry = _queue.Take(_cts.Token); }
                    catch (OperationCanceledException) { break; }
                    catch (InvalidOperationException) { break; }

                    WriteEncryptedLine(entry);

                    QueueEntry extra;
                    int batch = 0;
                    while (batch < _batchSize && _queue.TryTake(out extra))
                    {
                        WriteEncryptedLine(extra);
                        batch++;
                    }
                }
            }
            catch (Exception ex)
            {
                WriteToConsoleError("[EncryptedFileFlow] Writer error: " + ex.Message);
            }
            finally
            {
                QueueEntry remaining;
                while (_queue.TryTake(out remaining))
                {
                    WriteEncryptedLine(remaining);
                }

                lock (_lock) { CloseCurrentFile(); }
            }
        }

        private void FlushLoop()
        {
            while (!_cts.Token.IsCancellationRequested)
            {
                try { Thread.Sleep(_flushIntervalMs); }
                catch (ThreadInterruptedException) { break; }

                lock (_lock)
                {
                    if (_currentStream != null)
                    {
                        try { _currentStream.Flush(true); } catch { }
                    }
                }
            }
        }

        private void WriteEncryptedLine(QueueEntry entry)
        {
            // Deduct memory estimate
            long size = 200L + (entry.Line != null ? entry.Line.Length * 2 : 0);
            Interlocked.Add(ref _currentMemoryBytes, -size);

            lock (_lock)
            {
                DateTime today = _timestampMode == TimestampMode.Local
                    ? DateTime.Now.Date
                    : DateTime.UtcNow.Date;

                if (_currentStream == null || _currentDate != today || _currentSize > _maxFileSize)
                {
                    if (_currentStream != null)
                    {
                        Interlocked.Increment(ref _totalRotations);
                        Action<string> onRotate = _onRotate;
                        if (onRotate != null && _currentPath != null)
                        {
                            try { onRotate(_currentPath); }
                            catch { /* Do nothing */ }
                        }
                    }

                    CloseCurrentFile();
                    OpenNewFile(today);
                }

                try
                {
                    byte[] plain = Encoding.UTF8.GetBytes(entry.Line);
                    byte[] cipher = _encryptor.TransformFinalBlock(plain, 0, plain.Length);
                    byte[] lenBuf = BitConverter.GetBytes(cipher.Length);

                    _currentStream.Write(lenBuf, 0, 4);
                    _currentStream.Write(cipher, 0, cipher.Length);
                    _currentSize += 4 + cipher.Length;

                    Interlocked.Increment(ref _totalWritten);
                    Interlocked.Increment(ref BlastedCount);
                    Interlocked.Add(ref _totalBytesWritten, 4 + cipher.Length);

                    // Auto-flush on error/critical
                    if (_autoFlushOnError && entry.Level >= LogLevel.Error)
                    {
                        try { _currentStream.Flush(true); } catch { /* ignore */ }
                    }
                }
                catch (Exception ex)
                {
                    _lastError = ex;
                    Interlocked.Increment(ref _totalErrors);
                    Interlocked.Exchange(ref _lastErrorTimestamp, DateTime.UtcNow.Ticks);
                    WriteToConsoleError("[EncryptedFileFlow] Write error: " + ex.Message);
                }
            }
        }

        private void OpenNewFile(DateTime date)
        {
            _currentDate = date;
            _currentPath = Path.Combine(
                _directory,
                _filePrefix + "_" + Environment.MachineName + "_" + date.ToString(_dateFormat) + ".eona");

            bool isNew = !File.Exists(_currentPath) || new FileInfo(_currentPath).Length == 0;

            _currentStream = new FileStream(
                _currentPath, FileMode.Append, FileAccess.Write, FileShare.Read, 65536);

            byte[] salt = new byte[SaltSize];
            byte[] iv = new byte[IvSize];

            if (isNew)
            {
                using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
                {
                    rng.GetBytes(salt);
                    rng.GetBytes(iv);
                }
                _currentStream.Write(Magic, 0, 4);
                _currentStream.Write(salt, 0, SaltSize);
                _currentStream.Write(iv, 0, IvSize);
                _currentSize = 4 + SaltSize + IvSize;
            }
            else
            {
                // Re-read header so we continue the same key/IV session
                using (FileStream hdr = File.OpenRead(_currentPath))
                {
                    hdr.Seek(4, SeekOrigin.Begin);
                    ReadExact(hdr, salt, SaltSize);
                    ReadExact(hdr, iv, IvSize);
                }
                _currentSize = new FileInfo(_currentPath).Length;
            }

            byte[] key = DeriveKey(_password, salt);

            Aes aes = Aes.Create();
            aes.KeySize = 256;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;
            aes.Key = key;
            aes.IV = iv;
            _encryptor = aes.CreateEncryptor();
        }

        private void CloseCurrentFile()
        {
            if (_encryptor != null)
            {
                try { _encryptor.Dispose(); } catch { }
                _encryptor = null;
            }
            if (_currentStream != null)
            {
                try { _currentStream.Flush(true); _currentStream.Dispose(); } catch { }
                _currentStream = null;
            }
        }

        private WriteResult TryEnqueue(LogEvent logEvent)
        {
            long size = EstimateSize(logEvent);
            long current = Interlocked.Read(ref _currentMemoryBytes);

            if (current + size > _maxMemoryBytes)
            {
                Interlocked.Increment(ref DroppedCount);
                Action<LogEvent> drop = _onDrop;
                if (drop != null)
                {
                    drop(logEvent);
                }

                return WriteResult.Dropped;
            }

            string line = Format(logEvent);
            var entry = new QueueEntry { Line = line, Level = logEvent.Level };

            if (!_queue.TryAdd(entry))
            {
                Interlocked.Increment(ref DroppedCount);
                Action<LogEvent> drop = _onDrop;
                if (drop != null)
                {
                    drop(logEvent);
                }

                return WriteResult.Dropped;
            }

            Interlocked.Add(ref _currentMemoryBytes, size);
            Interlocked.Increment(ref BlastedCount);

            // Fan-out to secondary writers
            try
            {
                if (_secondaryWriters != null)
                {
                    lock (_secondaryWritersLock)
                    {
                        foreach (Action<LogEvent, string> writer in _secondaryWriters)
                        {
                            try { writer(logEvent, line); }
                            catch { /* Do nothing */ }
                        }
                    }
                }
            }
            catch { /* Do nothing */ }

            return WriteResult.Success;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool TryPassRateLimit()
        {
            long now = DateTime.UtcNow.Ticks;
            lock (_rateLimitLock)
            {
                long elapsed = now - _rateLimitWindowStart;
                if (elapsed >= TimeSpan.TicksPerSecond)
                {
                    _rateLimitWindowStart = now;
                    _rateLimitCounter = 1;
                    return true;
                }

                if (_rateLimitEnabled && _rateLimitCounter >= _maxEventsPerSecond)
                {
                    return false;
                }

                _rateLimitCounter++;
                return true;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool PassesFilters(LogEvent log)
        {
            lock (_filtersLock)
            {
                for (int i = 0; i < _filters.Count; i++)
                {
                    if (!_filters[i](log))
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        private bool IsDuplicate(LogEvent log)
        {
            string key = string.Concat(
                log.Level.ToString(), "|",
                log.Category ?? string.Empty, "|",
                log.Message.Length > 0 ? log.Message.ToString() : string.Empty);

            long nowTicks = DateTime.UtcNow.Ticks;
            long windowTicks = _deduplicationWindow.Ticks;

            long existing;
            if (_deduplicationCache.TryGetValue(key, out existing))
            {
                if (nowTicks - existing < windowTicks)
                {
                    return true;
                }
            }

            _deduplicationCache[key] = nowTicks;

            if (_deduplicationCache.Count > 10000)
            {
                CleanDeduplicationCache(nowTicks, windowTicks);
            }

            return false;
        }

        private void CleanDeduplicationCache(long nowTicks, long windowTicks)
        {
            foreach (var kvp in _deduplicationCache)
            {
                if (nowTicks - kvp.Value >= windowTicks)
                {
                    long removed;
                    _deduplicationCache.TryRemove(kvp.Key, out removed);
                }
            }
        }

        private void WriteToConsoleError(string text)
        {
            if (IgnoreConsoleErrors)
            {
                return;
            }

            Console.Error.WriteLine(text);
        }

        private string Format(LogEvent log)
        {
            var sb = new StringBuilder(256);
            foreach (Action<LogEvent, StringBuilder> action in _compiledTemplate)
            {
                action(log, sb);
            }
            return sb.ToString();
        }

        private void AppendProperties(LogEvent log, StringBuilder sb)
        {
            var scopeProps = _scopeProperties.Value;
            bool hasEnrichers = _enrichers.Count > 0;
            bool hasProps = log.Properties.Count > 0;
            bool hasScope = scopeProps != null && scopeProps.Count > 0;
            if (!hasEnrichers && !hasProps && !hasScope)
            {
                return;
            }

            sb.Append(" {");
            bool first = true;

            foreach (KeyValuePair<string, Func<LogEvent, object>> kv in _enrichers)
            {
                if (!first)
                {
                    sb.Append(", ");
                }

                first = false;
                object val = kv.Value(log);
                sb.Append(kv.Key).Append('=').Append(val != null ? val.ToString() : "null");
            }

            foreach (var property in log.Properties)
            {
                if (!first)
                {
                    sb.Append(", ");
                }

                first = false;
                sb.Append(property.Key).Append('=')
                  .Append(property.Value != null ? property.Value.ToString() : "null");
            }

            if (hasScope)
            {
                foreach (var kv in scopeProps)
                {
                    if (!first)
                    {
                        sb.Append(", ");
                    }

                    first = false;
                    sb.Append(kv.Key).Append('=')
                      .Append(kv.Value != null ? kv.Value.ToString() : "null");
                }
            }

            sb.Append('}');
        }

        private void CompileTemplate(string template)
        {
            _compiledTemplate = new List<Action<LogEvent, StringBuilder>>();
            int pos = 0;

            while (pos < template.Length)
            {
                int open = template.IndexOf('{', pos);
                if (open < 0)
                {
                    string lit = template.Substring(pos);
                    _compiledTemplate.Add((_, sb) => sb.Append(lit));
                    break;
                }

                if (open > pos)
                {
                    string lit = template.Substring(pos, open - pos);
                    _compiledTemplate.Add((_, sb) => sb.Append(lit));
                }

                int close = template.IndexOf('}', open);
                if (close < 0)
                {
                    string lit = template.Substring(open);
                    _compiledTemplate.Add((_, sb) => sb.Append(lit));
                    break;
                }

                string token = template.Substring(open + 1, close - (open + 1));
                _compiledTemplate.Add(ResolveToken(token));
                pos = close + 1;
            }
        }

        private Action<LogEvent, StringBuilder> ResolveToken(string token)
        {
            switch (token.ToLowerInvariant())
            {
                case "ts":
                    return (log, sb) =>
                        sb.Append(LogEvent.GetDateTime(log.Timestamp).ToString("yyyy-MM-dd HH:mm:ss.fff"));
                case "tz":
                    return (log, sb) =>
                        sb.Append(_timestampMode == TimestampMode.Local
                            ? TimeZoneInfo.Local.StandardName : "UTC");
                case "host":
                    return (log, sb) => sb.Append(CachedMachineName);
                case "category":
                    return (log, sb) => { if (log.Category != null) { sb.Append(log.Category); } };
                case "thread":
                    return (log, sb) => sb.Append(Thread.CurrentThread.ManagedThreadId);
                case "logtype":
                    return (log, sb) =>
                    {
                        string s;
                        sb.Append(LevelStrings.TryGetValue(log.Level, out s) ? s : log.Level.ToString());
                    };
                case "message":
                    return (log, sb) =>
                    {
                        if (log.Message.Length > 0)
                        {
                            sb.Append(log.Message.ToString());
                        }
                    };
                case "exception":
                    return (log, sb) =>
                    {
                        if (log.Exception != null)
                        {
                            sb.Append(log.Exception.ToString());
                        }
                    };
                case "props":
                    return (log, sb) => AppendProperties(log, sb);
                case "newline":
                    return (log, sb) => sb.AppendLine();
                case "pid":
                    return (log, sb) => sb.Append(CachedPid);
                case "traceid":
                    return (log, sb) =>
                    {
                        if (log.TraceId != default(ActivityTraceId))
                        {
                            sb.Append(log.TraceId.ToHexString());
                        }
                    };
                case "spanid":
                    return (log, sb) =>
                    {
                        if (log.SpanId != default(ActivitySpanId))
                        {
                            sb.Append(log.SpanId.ToHexString());
                        }
                    };
                default:
                    return BuildCustomOrLiteralToken(token);
            }
        }

        private Action<LogEvent, StringBuilder> BuildCustomOrLiteralToken(string token)
        {
            string name = token;
            return (log, sb) =>
            {
                Action<LogEvent, StringBuilder> custom;
                if (_customTokens.TryGetValue(name, out custom))
                {
                    custom(log, sb);
                }
                else
                {
                    sb.Append('{').Append(name).Append('}');
                }
            };
        }

        private void RetentionLoop()
        {
            while (!_cts.Token.IsCancellationRequested)
            {
                try
                {
                    if (_cts.Token.WaitHandle.WaitOne(TimeSpan.FromMinutes(15)))
                    {
                        break;
                    }

                    ApplyRetention();
                }
                catch (Exception ex)
                {
                    WriteToConsoleError("[EncryptedFileFlow] Retention error: " + ex.Message);
                }
            }
        }

        private void ApplyRetention()
        {
            try
            {
                DirectoryInfo dir = new DirectoryInfo(_directory);
                if (!dir.Exists)
                {
                    return;
                }

                FileInfo[] files = dir.GetFiles("*.eona")
                    .OrderByDescending(f => f.LastWriteTimeUtc)
                    .ToArray();

                long totalBytes = 0;
                int kept = 0;

                foreach (FileInfo f in files)
                {
                    bool tooOld = _retention.MaxAgeDays > 0
                                 && (DateTime.UtcNow - f.LastWriteTimeUtc).TotalDays > _retention.MaxAgeDays;
                    bool tooMany = _retention.MaxRolledFiles > 0 && kept >= _retention.MaxRolledFiles;
                    bool tooLarge = _retention.MaxTotalArchiveBytes > 0
                                 && totalBytes + f.Length > _retention.MaxTotalArchiveBytes;
                    bool directoryTooLarge = totalBytes + f.Length > _maxDirectorySize;

                    if (tooOld || tooMany || tooLarge || directoryTooLarge)
                    {
                        try { f.Delete(); } catch { /* ignore */ }
                    }
                    else
                    {
                        totalBytes += f.Length;
                        kept++;
                    }
                }
            }
            catch (Exception ex)
            {
                WriteToConsoleError("[EncryptedFileFlow] Retention error: " + ex.Message);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static long EstimateSize(LogEvent log)
        {
            long s = 200L
                   + log.Message.Length * 2
                   + (log.Category != null ? log.Category.Length * 2 : 0)
                   + log.Properties.Count * 40;
            if (log.Exception != null)
            {
                s += 2048;
            }

            return s;
        }

        private static string LevelString(LogLevel level)
        {
            switch (level)
            {
                case LogLevel.Trace: return "TRACE";
                case LogLevel.Debug: return "DEBUG";
                case LogLevel.Information: return "INFO";
                case LogLevel.Warning: return "WARN";
                case LogLevel.Error: return "ERROR";
                case LogLevel.Critical: return "CRITICAL";
                default: return level.ToString().ToUpperInvariant();
            }
        }

        private static byte[] DeriveKey(string password, byte[] salt)
        {
            using (Rfc2898DeriveBytes kdf = new Rfc2898DeriveBytes(password, salt, Pbkdf2Iter))
            {
                return kdf.GetBytes(KeySize);
            }
        }

        private static void ReadExact(Stream stream, byte[] buf, int count)
        {
            int offset = 0;
            while (offset < count)
            {
                int r = stream.Read(buf, offset, count - offset);
                if (r == 0)
                {
                    throw new EndOfStreamException("Unexpected end of encrypted log stream.");
                }

                offset += r;
            }
        }
    }
}