using EonaCat.Json;
using EonaCat.LogStack.Extensions;
using EonaCat.LogStack.LogClient.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace EonaCat.LogStack.LogClient
{
    public class EonaCatPayLoad
    {
        public List<EonaCatLogEvent>? Events { get; set; }
    }

    public class EonaCatLogEvent
    {
        public string Timestamp { get; set; } = default!;
        public string Level { get; set; } = default!;
        public string Message { get; set; } = default!;
        public string Category { get; set; } = default!;
        public ExceptionDto? Exception { get; set; }
        public Dictionary<string, object?>? Properties { get; set; }
    }

    public class ExceptionDto
    {
        public string Type { get; set; } = default!;
        public string Message { get; set; } = default!;
        public string? StackTrace { get; set; }
    }

    public class LogCentralClient : IDisposable
    {
        private readonly HttpClient _httpClient;
        private readonly LogCentralOptions _options;
        private readonly ConcurrentQueue<LogEntry> _logQueue;
        private readonly Timer _flushTimer;
        private readonly SemaphoreSlim _flushSemaphore;
        private bool _disposed;

        public LogCentralClient(LogCentralOptions options)
        {
            _options = options ?? throw new ArgumentNullException(nameof(options));
            _httpClient = new HttpClient { BaseAddress = new Uri(_options.ServerUrl) };
            _httpClient.DefaultRequestHeaders.Add("X-API-Key", _options.ApiKey);
            _logQueue = new ConcurrentQueue<LogEntry>();
            _flushSemaphore = new SemaphoreSlim(1, 1);
            _flushTimer = new Timer(async _ => await FlushAsync(), null,
                TimeSpan.FromSeconds(_options.FlushIntervalSeconds),
                TimeSpan.FromSeconds(_options.FlushIntervalSeconds));
        }

        public async Task LogAsync(LogEntry entry)
        {
            entry.Source = _options.ApplicationName;
            entry.Timestamp = DateTime.UtcNow;
            entry.Message ??= "";

            var properties = new Dictionary<string, object?>();
            if (_options.ApplicationName != null)
            {
                properties.Add("ApplicationName", _options.ApplicationName);
            }

            if (_options.ApplicationVersion != null)
            {
                properties.Add("ApplicationVersion", _options.ApplicationVersion);
            }

            if (_options.Environment != null)
            {
                properties.Add("Environment", _options.Environment);
            }

            if (!string.IsNullOrEmpty(entry.TraceId))
            {
                properties.Add("TraceId", entry.TraceId);
            }

            entry.Properties = JsonHelper.ToJson(properties);

            _logQueue.Enqueue(entry);

            if (_logQueue.Count >= _options.BatchSize)
            {
                await FlushAsync();
            }
        }

        private async Task FlushAsync()
        {
            if (_logQueue.IsEmpty)
            {
                return;
            }

            await _flushSemaphore.WaitAsync();
            try
            {
                var batch = new List<LogEntry>();
                while (batch.Count < _options.BatchSize && _logQueue.TryDequeue(out var entry))
                {
                    batch.Add(entry);
                }

                if (batch.Count > 0)
                {
                    await SendBatchToEonaCatAsync(batch);
                }
            }
            finally
            {
                _flushSemaphore.Release();
            }
        }

        private async Task SendBatchToEonaCatAsync(List<LogEntry> batch)
        {
            try
            {
                var eventsArray = batch.Select(e => new
                {
                    timestamp = e.Timestamp.ToString("O"),
                    level = e.Level,
                    message = e.Message ?? "", // empty message is fine
                    exception = string.IsNullOrEmpty(e.Exception) ? null : new
                    {
                        type = "Exception",
                        message = e.Exception,
                        stackTrace = e.StackTrace
                    },
                    properties = string.IsNullOrEmpty(e.Properties)
                        ? new Dictionary<string, object?>() // <-- same type now
                        : JsonHelper.ToObject<Dictionary<string, object?>>(e.Properties)
                }).ToArray();

                var json = JsonHelper.ToJson(eventsArray);

                using var content = new StringContent(json, Encoding.UTF8, "application/json");
                var response = await _httpClient.PostAsync("api/logs/eonacat", content);

                var responseContent = await response.Content.ReadAsStringAsync();
                response.EnsureSuccessStatusCode();
            }
            catch (Exception ex)
            {
                if (_options.EnableFallbackLogging)
                {
                    Console.WriteLine($"[LogCentral] Failed to send logs to EonaCat: {ex.Message}");
                }

                foreach (var entry in batch)
                {
                    _logQueue.Enqueue(entry);
                }
            }
        }

        public async Task FlushAndDisposeAsync()
        {
            await FlushAsync();
            Dispose();
        }

        public void Dispose()
        {
            if (_disposed)
            {
                return;
            }

            _flushTimer?.Dispose();
            FlushAsync().GetAwaiter().GetResult();
            _httpClient?.Dispose();
            _flushSemaphore?.Dispose();
            _disposed = true;
            GC.SuppressFinalize(this);
        }
    }
}