using EonaCat.Json;
using EonaCat.LogStack.Core;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Channels;
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>
    /// logging flow that sends messages to a Telegram chat via a bot.
    /// </summary>
    public sealed class TelegramFlow : FlowBase, IAsyncDisposable
    {
        private const int ChannelCapacity = 4096;
        private readonly int _batchSize;

        private readonly Channel<LogEvent> _channel;
        private readonly Task _workerTask;
        private readonly CancellationTokenSource _cts;
        private readonly HttpClient _httpClient;
        private readonly string _botToken;
        private readonly string _chatId;

        public TelegramFlow(
            string botToken,
            string chatId,
            int batchSize = 1,
            LogLevel minimumLevel = LogLevel.Information)
            : base("Telegram", minimumLevel)
        {
            _botToken = botToken ?? throw new ArgumentNullException(nameof(botToken));
            _chatId = chatId ?? throw new ArgumentNullException(nameof(chatId));
            _httpClient = new HttpClient();

            var channelOptions = new BoundedChannelOptions(ChannelCapacity)
            {
                FullMode = BoundedChannelFullMode.DropOldest,
                SingleReader = true,
                SingleWriter = false
            };

            _batchSize = batchSize <= 0 ? 1 : batchSize;
            _channel = Channel.CreateBounded<LogEvent>(channelOptions);
            _cts = new CancellationTokenSource();
            _workerTask = Task.Run(() => ProcessQueueAsync(_cts.Token));
        }

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

            if (_channel.Writer.TryWrite(logEvent))
            {
                Interlocked.Increment(ref BlastedCount);
                return Task.FromResult(WriteResult.Success);
            }

            Interlocked.Increment(ref DroppedCount);
            return Task.FromResult(WriteResult.Dropped);
        }

        private async Task ProcessQueueAsync(CancellationToken cancellationToken)
        {
            var batch = new List<LogEvent>(_batchSize);

            try
            {
                while (await _channel.Reader.WaitToReadAsync(cancellationToken))
                {
                    while (_channel.Reader.TryRead(out var logEvent))
                    {
                        batch.Add(logEvent);

                        if (batch.Count >= _batchSize)
                        {
                            await SendBatchAsync(batch, cancellationToken);
                            batch.Clear();
                        }
                    }

                    if (batch.Count > 0)
                    {
                        await SendBatchAsync(batch, cancellationToken);
                        batch.Clear();
                    }
                }

                if (batch.Count > 0)
                {
                    await SendBatchAsync(batch, cancellationToken);
                }
            }
            catch (OperationCanceledException) { }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"TelegramFlow error: {ex.Message}");
            }
        }

        private async Task SendBatchAsync(List<LogEvent> batch, CancellationToken cancellationToken)
        {
            foreach (var logEvent in batch)
            {
                var message = BuildMessage(logEvent);

                var url = $"https://api.telegram.org/bot{_botToken}/sendMessage";

                var payload = new
                {
                    chat_id = _chatId,
                    text = message,
                    parse_mode = "Markdown"
                };

                var json = JsonHelper.ToJson(payload);
                using var content = new StringContent(json, Encoding.UTF8, "application/json");
                await _httpClient.PostAsync(url, content, cancellationToken);
            }
        }

        private static string BuildMessage(LogEvent logEvent)
        {
            var sb = new StringBuilder();
            sb.Append($"*{logEvent.Level}* | {logEvent.Category}\n");
            sb.Append($"`{LogEvent.GetDateTime(logEvent.Timestamp):yyyy-MM-dd HH:mm:ss.fff}`\n");
            sb.Append(logEvent.Message);

            if (logEvent.Exception != null)
            {
                sb.Append($"\n*Exception:* `{logEvent.Exception.GetType().FullName}`\n");
                sb.Append($"`{logEvent.Exception.Message}`\n");
            }

            if (logEvent.Properties.Count > 0)
            {
                sb.Append("\n*Properties:*");
                foreach (var prop in logEvent.Properties)
                {
                    sb.Append($"\n`{prop.Key}` = `{prop.Value?.ToString() ?? "null"}`");
                }
            }

            return sb.ToString();
        }

        public override async Task FlushAsync(CancellationToken cancellationToken = default)
        {
            _channel.Writer.Complete();
            try
            {
                await _workerTask.ConfigureAwait(false);
            }
            catch { }
        }

        public override async ValueTask DisposeAsync()
        {
            IsEnabled = false;
            _channel.Writer.Complete();
            _cts.Cancel();

            try
            {
                await _workerTask.ConfigureAwait(false);
            }
            catch { }

            _httpClient.Dispose();
            _cts.Dispose();
            await base.DisposeAsync();
        }
    }
}
