using EonaCat.LogStack.Core;
using EonaCat.LogStack.EonaCatLogStackCore;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Mail;
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>
    /// Sends log events as email via SMTP.
    ///
    /// Includes built-in digest batching: instead of one email per event, events are
    /// accumulated for up to <see cref="DigestInterval"/> and sent as a single digest.
    /// A "flush-on-critical" option bypasses batching for Critical events.
    /// </summary>
    public sealed class EmailFlow : FlowBase
    {
        private readonly string _headerName = "<h2>EonaCat Logger – Log Digest</h2>";
        private readonly string _smtpHost;
        private readonly int _smtpPort;
        private readonly bool _useSsl;
        private readonly string _username;
        private readonly string _password;
        private readonly string _from;
        private readonly string[] _to;
        private readonly string _subjectPrefix;
        private readonly TimeSpan _digestInterval;
        private readonly bool _flushOnCritical;
        private readonly int _maxEventsPerDigest;

        private readonly List<LogEvent> _pending = new List<LogEvent>();
        private readonly object _lock = new object();
        private DateTime _lastSent = DateTime.UtcNow;

        private readonly CancellationTokenSource _cts = new CancellationTokenSource();
        private readonly Thread _digestThread;

        private long _totalEmails;

        public EmailFlow(
            string smtpHost,
            int smtpPort = 587,
            bool useSsl = true,
            string username = null,
            string password = null,
            string from = null,
            string to = null,
            string subjectPrefix = "[EonaCatLogStack]",
            int digestMinutes = 5,
            bool flushOnCritical = true,
            int maxEventsPerDigest = 100,
            string headerName = null,
            LogLevel minimumLevel = LogLevel.Error)
            : base("Email:" + smtpHost, minimumLevel)
        {
            if (smtpHost == null)
            {
                throw new ArgumentNullException("smtpHost");
            }

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

            if (string.IsNullOrWhiteSpace(headerName))
            {
                _headerName = headerName;
            }

            _smtpHost = smtpHost;
            _smtpPort = smtpPort;
            _useSsl = useSsl;
            _username = username;
            _password = password;
            _from = from ?? ("eonacat-logger@" + smtpHost);
            _to = to.Split(new char[] { ',', ';' },
                                     StringSplitOptions.RemoveEmptyEntries);
            _subjectPrefix = subjectPrefix ?? "[EonaCatLogStack]";
            _digestInterval = TimeSpan.FromMinutes(digestMinutes < 1 ? 1 : digestMinutes);
            _flushOnCritical = flushOnCritical;
            _maxEventsPerDigest = maxEventsPerDigest < 1 ? 1 : maxEventsPerDigest;

            _digestThread = new Thread(DigestLoop)
            {
                IsBackground = true,
                Name = "EmailFlow.Digest",
                Priority = ThreadPriority.BelowNormal
            };
            _digestThread.Start();
        }

        public long TotalEmailsSent { get { return Interlocked.Read(ref _totalEmails); } }

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

            bool sendNow = false;
            lock (_lock)
            {
                _pending.Add(logEvent);
                if (_flushOnCritical && logEvent.Level >= LogLevel.Critical)
                {
                    sendNow = true;
                }

                if (_pending.Count >= _maxEventsPerDigest)
                {
                    sendNow = true;
                }
            }

            if (sendNow)
            {
                SendDigestAsync();
            }

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

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

            foreach (LogEvent e in logEvents.ToArray())
            {
                if (IsLogLevelEnabled(e))
                {
                    BlastAsync(e, cancellationToken);
                }
            }

            return Task.FromResult(WriteResult.Success);
        }

        public override Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            SendDigestAsync();
            return Task.FromResult(0);
        }

        public override async ValueTask DisposeAsync()
        {
            IsEnabled = false;
            _cts.Cancel();
            _digestThread.Join(TimeSpan.FromSeconds(5));
            SendDigestAsync();
            _cts.Dispose();
            await base.DisposeAsync().ConfigureAwait(false);
        }

        private void DigestLoop()
        {
            while (!_cts.Token.IsCancellationRequested)
            {
                try { Thread.Sleep(TimeSpan.FromSeconds(30)); }
                catch (ThreadInterruptedException) { break; }

                lock (_lock)
                {
                    if (_pending.Count > 0 && DateTime.UtcNow - _lastSent >= _digestInterval)
                    {
                        SendDigestAsync();
                    }
                }
            }
        }

        private void SendDigestAsync()
        {
            List<LogEvent> batch;
            lock (_lock)
            {
                if (_pending.Count == 0)
                {
                    return;
                }

                batch = new List<LogEvent>(_pending);
                _pending.Clear();
                _lastSent = DateTime.UtcNow;
            }

            // Fire-and-forget on a thread pool thread
            ThreadPool.QueueUserWorkItem(_ => SendDigest(batch));
        }

        private void SendDigest(List<LogEvent> events)
        {
            try
            {
                string subject = BuildSubject(events);
                string body = BuildBody(events);

                using (SmtpClient smtp = new SmtpClient(_smtpHost, _smtpPort))
                {
                    smtp.EnableSsl = _useSsl;
                    if (!string.IsNullOrEmpty(_username))
                    {
                        smtp.Credentials = new NetworkCredential(_username, _password);
                    }

                    using (MailMessage msg = new MailMessage())
                    {
                        msg.From = new MailAddress(_from);
                        msg.Subject = subject;
                        msg.Body = body;
                        msg.IsBodyHtml = true;

                        foreach (string addr in _to)
                        {
                            msg.To.Add(addr.Trim());
                        }

                        smtp.Send(msg);
                    }
                }

                Interlocked.Increment(ref _totalEmails);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine("[EmailFlow] Send error: " + ex.Message);
                Interlocked.Increment(ref DroppedCount);
            }
        }

        private string BuildSubject(List<LogEvent> events)
        {
            LogLevel maxLevel = LogLevel.Trace;
            foreach (LogEvent e in events)
            {
                if (e.Level > maxLevel)
                {
                    maxLevel = e.Level;
                }
            }

            return _subjectPrefix + " " + LevelString(maxLevel) +
                   " – " + events.Count + " event(s) @ " +
                   DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + " UTC";
        }

        private string BuildBody(List<LogEvent> events)
        {
            var sb = new StringBuilder(events.Count * 300);
            sb.AppendLine("<html><body style='font-family:monospace;font-size:13px'>");
            sb.AppendLine(_headerName);
            sb.AppendLine("<table border='1' cellpadding='4' cellspacing='0' style='border-collapse:collapse;width:100%'>");
            sb.AppendLine("<tr style='background:#333;color:white'>" +
                          "<th>Time</th><th>Level</th><th>Category</th>" +
                          "<th>Message</th><th>Exception</th></tr>");

            foreach (LogEvent e in events)
            {
                string color = LevelColor(e.Level);
                string ts = LogEvent.GetDateTime(e.Timestamp).ToString("HH:mm:ss.fff");
                string msg = HtmlEncode(e.Message.Length > 0 ? e.Message.ToString() : string.Empty);
                string exc = e.Exception != null
                               ? HtmlEncode(e.Exception.GetType().Name + ": " + e.Exception.Message)
                               : string.Empty;

                sb.AppendFormat(
                    "<tr style='background:{0}'><td>{1}</td><td><b>{2}</b></td><td>{3}</td><td>{4}</td><td>{5}</td></tr>",
                    color, ts, LevelString(e.Level),
                    HtmlEncode(e.Category ?? string.Empty), msg, exc);
                sb.AppendLine();
            }

            sb.AppendLine("</table></body></html>");
            return sb.ToString();
        }

        private static string LevelColor(LogLevel level)
        {
            switch (level)
            {
                case LogLevel.Warning: return "#FFF3CD";
                case LogLevel.Error: return "#F8D7DA";
                case LogLevel.Critical: return "#F1AEB5";
                default: return "#FFFFFF";
            }
        }

        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 string HtmlEncode(string s)
        {
            if (string.IsNullOrEmpty(s))
            {
                return string.Empty;
            }

            return s.Replace("&", "&amp;")
                    .Replace("<", "&lt;")
                    .Replace(">", "&gt;")
                    .Replace("\"", "&quot;");
        }
    }
}