using EonaCat.LogStack.Core;
using System;
using System.IO;
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>
    /// A flow that writes logs to files with automatic date-based rotation.
    /// Creates new log files daily, hourly, or at custom intervals without external dependencies.
    /// </summary>
    public sealed class RotatingFileFlow : FlowBase
    {
        public enum RotationFrequency
        {
            Hourly = 0,
            Daily = 1,
            Monthly = 2
        }

        private readonly string _baseDirectory;
        private readonly string _fileNamePattern;
        private readonly RotationFrequency _frequency;
        private readonly Encoding _encoding;
        private readonly object _fileLock = new();
        private StreamWriter? _currentWriter;
        private string _currentFileName = string.Empty;
        private DateTime _lastRotationCheck = DateTime.MinValue;

        public RotatingFileFlow(
            string baseDirectory,
            string fileNamePattern = "logs-{date}.txt",
            RotationFrequency frequency = RotationFrequency.Daily,
            Encoding? encoding = null,
            LogLevel minimumLevel = LogLevel.Trace)
            : base("RotatingFileFlow", minimumLevel)
        {
            _baseDirectory = baseDirectory ?? throw new ArgumentNullException(nameof(baseDirectory));
            _fileNamePattern = fileNamePattern ?? throw new ArgumentNullException(nameof(fileNamePattern));
            _frequency = frequency;
            _encoding = encoding ?? Encoding.UTF8;

            try
            {
                if (!Directory.Exists(_baseDirectory))
                {
                    Directory.CreateDirectory(_baseDirectory);
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"RotatingFileFlow directory creation error: {ex.Message}");
            }
        }

        private string GetCurrentFileName()
        {
            var now = DateTime.Now;
            string datePart = _frequency switch
            {
                RotationFrequency.Hourly => now.ToString("yyyy-MM-dd_HH"),
                RotationFrequency.Monthly => now.ToString("yyyy-MM"),
                RotationFrequency.Daily => now.ToString("yyyy-MM-dd"),
                _ => now.ToString("yyyy-MM-dd")
            };

            var fileName = _fileNamePattern.Replace("{date}", datePart);
            return Path.Combine(_baseDirectory, fileName);
        }

        private bool ShouldRotate()
        {
            var now = DateTime.Now;
            var timeSinceLastCheck = now - _lastRotationCheck;

            return _frequency switch
            {
                RotationFrequency.Hourly => timeSinceLastCheck.TotalHours >= 1,
                RotationFrequency.Monthly => timeSinceLastCheck.TotalDays >= 30,
                RotationFrequency.Daily => timeSinceLastCheck.TotalDays >= 1,
                _ => false
            };
        }

        private async Task EnsureWriterAsync()
        {
            var targetFileName = GetCurrentFileName();

            if (_currentWriter != null && _currentFileName == targetFileName && !ShouldRotate())
            {
                return;
            }

            _lastRotationCheck = DateTime.Now;

            try
            {
                _currentWriter?.Dispose();

                _currentFileName = targetFileName;
                var fileStream = new FileStream(
                    _currentFileName,
                    FileMode.Append,
                    FileAccess.Write,
                    FileShare.Read,
                    4096,
                    FileOptions.SequentialScan);

                _currentWriter = new StreamWriter(fileStream, _encoding, 4096, false);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"RotatingFileFlow writer creation error: {ex.Message}");
                throw;
            }
        }

        public override async Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
        {
            if (!IsEnabled || !IsLogLevelEnabled(logEvent))
            {
                return WriteResult.LevelFiltered;
            }

            lock (_fileLock)
            {
                try
                {
                    EnsureWriterAsync().Wait(cancellationToken);

                    if (_currentWriter == null)
                    {
                        Interlocked.Increment(ref DroppedCount);
                        return WriteResult.Failed;
                    }

                    var formattedMessage = FormatLogEvent(logEvent);
                    _currentWriter.WriteLine(formattedMessage);
                    _currentWriter.Flush();

                    Interlocked.Increment(ref BlastedCount);
                    return WriteResult.Success;
                }
                catch (Exception ex)
                {
                    Interlocked.Increment(ref DroppedCount);
                    System.Diagnostics.Debug.WriteLine($"RotatingFileFlow write error: {ex.Message}");
                    return WriteResult.Failed;
                }
            }
        }

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

            lock (_fileLock)
            {
                try
                {
                    EnsureWriterAsync().Wait(cancellationToken);

                    if (_currentWriter == null)
                    {
                        Interlocked.Add(ref DroppedCount, logEvents.Length);
                        return WriteResult.Failed;
                    }

                    foreach (var logEvent in logEvents.Span)
                    {
                        if (IsLogLevelEnabled(logEvent))
                        {
                            var formattedMessage = FormatLogEvent(logEvent);
                            _currentWriter.WriteLine(formattedMessage);
                            Interlocked.Increment(ref BlastedCount);
                        }
                        else
                        {
                            Interlocked.Increment(ref DroppedCount);
                        }
                    }

                    _currentWriter.Flush();
                    return WriteResult.Success;
                }
                catch (Exception ex)
                {
                    Interlocked.Add(ref DroppedCount, logEvents.Length);
                    System.Diagnostics.Debug.WriteLine($"RotatingFileFlow batch write error: {ex.Message}");
                    return WriteResult.Failed;
                }
            }
        }

        private string FormatLogEvent(LogEvent logEvent)
        {
            var sb = new StringBuilder();
            var dateTime = LogEvent.GetDateTime(logEvent.Timestamp);
            sb.Append($"[{dateTime:yyyy-MM-dd HH:mm:ss.fff}] ");
            sb.Append($"[{logEvent.Level}] ");
            if (!string.IsNullOrEmpty(logEvent.Category))
            {
                sb.Append($"[{logEvent.Category}] ");
            }
            sb.Append(logEvent.Message);

            if (logEvent.Exception != null)
            {
                sb.AppendLine();
                sb.Append("Exception: ");
                sb.Append(logEvent.Exception.GetType().Name);
                sb.Append(": ");
                sb.Append(logEvent.Exception.Message);
            }

            return sb.ToString();
        }

        public override async Task FlushAsync(CancellationToken cancellationToken = default)
        {
            lock (_fileLock)
            {
                _currentWriter?.Flush();
            }
        }

        public override async ValueTask DisposeAsync()
        {
            lock (_fileLock)
            {
                _currentWriter?.Flush();
                _currentWriter?.Dispose();
                _currentWriter = null;
            }

            await base.DisposeAsync().ConfigureAwait(false);
        }
    }
}
