using EonaCat.Json;
using EonaCat.LogStack.Core;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using System.Net.Security;

// 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.

namespace EonaCat.LogStack.Flows
{
    public sealed class EventLogFlow : FlowBase
    {
        private readonly string _destination;
        private readonly int _port;
        private TcpClient? _tcpClient;
        private NetworkStream? _stream;
        private SslStream? _sslStream;
        private readonly bool _useTls;
        private readonly RemoteCertificateValidationCallback? _certificateValidationCallback;
        private readonly X509CertificateCollection? _clientCertificates;

        private readonly List<LogEvent> _logBuffer;
        private readonly int _bufferSize;
        private readonly TimeSpan _flushInterval;
        private readonly CancellationTokenSource _cts;

        public EventLogFlow(
            string destination,
            int port = 514,
            LogLevel minimumLevel = LogLevel.Trace,
            int bufferSize = 100,
            TimeSpan? flushInterval = null,
            bool useTls = false,
            RemoteCertificateValidationCallback? certificateValidationCallback = null,
            X509CertificateCollection? clientCertificates = null
        ) : base($"EventLogFlow:{destination}:{port}", minimumLevel)
        {
            _destination = destination ?? throw new ArgumentNullException(nameof(destination));
            _port = port;
            _useTls = useTls;
            _certificateValidationCallback = certificateValidationCallback;
            _clientCertificates = clientCertificates;
            _bufferSize = bufferSize;
            _flushInterval = flushInterval ?? TimeSpan.FromSeconds(5);
            _logBuffer = new List<LogEvent>(bufferSize);
            _cts = new CancellationTokenSource();

            _tcpClient = new TcpClient();
            _ = StartFlushingLogsAsync(_cts.Token);
        }

        public void Log(string message, string category = "CustomEvent", LogLevel level = LogLevel.Information, object customData = null)
        {
            var logEvent = new LogEvent
            {
                Timestamp = DateTime.UtcNow.Ticks,
                Level = level,
                Message = message.ToCharArray(),
                Category = category,
                CustomData = customData != null ? JsonHelper.ToJson(customData) : string.Empty
            };

            // Add to buffer and trigger flush if needed
            _logBuffer.Add(logEvent);
            if (_logBuffer.Count >= _bufferSize)
            {
                _ = FlushLogsAsync();
            }
        }

        private async Task StartFlushingLogsAsync(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                await Task.Delay(_flushInterval, cancellationToken);

                if (_logBuffer.Count > 0)
                {
                    await FlushLogsAsync();
                }
            }
        }

        private async Task FlushLogsAsync()
        {
            if (_logBuffer.Count == 0)
            {
                return;
            }

            var logsToSend = new List<LogEvent>(_logBuffer);
            _logBuffer.Clear();

            await SendLogsAsync(logsToSend);
        }

        private async Task SendLogsAsync(IEnumerable<LogEvent> logEvents)
        {
            try
            {
                await EnsureConnectedAsync();

                var logMessages = new StringBuilder();
                foreach (var logEvent in logEvents)
                {
                    logMessages.AppendLine(FormatLogMessage(logEvent));
                }

                var data = Encoding.UTF8.GetBytes(logMessages.ToString());
                if (_useTls && _sslStream != null)
                {
                    await _sslStream.WriteAsync(data, 0, data.Length);
                    await _sslStream.FlushAsync();
                }
                else if (_stream != null)
                {
                    await _stream.WriteAsync(data, 0, data.Length);
                    await _stream.FlushAsync();
                }
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"Error sending logs: {ex.Message}");
            }
        }

        private async Task EnsureConnectedAsync()
        {
            if (_tcpClient?.Connected ?? false)
            {
                return;
            }

            await _tcpClient?.ConnectAsync(_destination, _port);

            if (_useTls)
            {
                _stream = _tcpClient?.GetStream();
                _sslStream = new SslStream(_stream, false, _certificateValidationCallback);
                await _sslStream.AuthenticateAsClientAsync(_destination, _clientCertificates, System.Security.Authentication.SslProtocols.Tls12, false);
            }
            else
            {
                _stream = _tcpClient?.GetStream();
            }
        }

        private string FormatLogMessage(LogEvent logEvent)
        {
            var dt = LogEvent.GetDateTime(logEvent.Timestamp);
            var sb = new StringBuilder();

            sb.Append(dt.ToString("yyyy-MM-dd HH:mm:ss.fff"));
            sb.Append(" [");
            sb.Append(logEvent.Level.ToString().ToUpperInvariant());
            sb.Append("] ");
            sb.Append(logEvent.Category);
            sb.Append(": ");
            sb.Append(logEvent.Message);

            if (!string.IsNullOrEmpty(logEvent.CustomData))
            {
                sb.Append(" | CustomData: ");
                sb.Append(logEvent.CustomData);
            }

            return sb.ToString();
        }

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

            await SendLogsAsync(new List<LogEvent> { logEvent });
            return WriteResult.Success;
        }

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

            await SendLogsAsync(logEvents.Span.ToArray());
            return WriteResult.Success;
        }

        public override async ValueTask DisposeAsync()
        {
            _cts.Cancel();
            _sslStream?.Dispose();
            _stream?.Dispose();
            _tcpClient?.Dispose();
            await base.DisposeAsync();
        }

        public override Task FlushAsync(CancellationToken cancellationToken = default)
        {
            return Task.CompletedTask;
        }
    }
}