using EonaCat.Logger.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.Logger.LogClient
{
    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.ApplicationName = _options.ApplicationName;
            entry.ApplicationVersion = _options.ApplicationVersion;
            entry.Environment = _options.Environment;
            entry.Timestamp = DateTime.UtcNow;

            entry.MachineName ??= Environment.MachineName;
            entry.Category ??= entry.Category ?? "Default";
            entry.Message ??= entry.Message ?? "";

            _logQueue.Enqueue(entry);

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

        public void Log(LogEntry entry) => LogAsync(entry).GetAwaiter().GetResult();

        public async Task LogExceptionAsync(Exception ex, string message = "",
            Dictionary<string, object>? properties = null)
        {
            await LogAsync(new LogEntry
            {
                Level = (int)LogLevel.Error,
                Category = "Exception",
                Message = message,
                Exception = ex.ToString(),
                StackTrace = ex.StackTrace,
                Properties = properties
            });
        }

        public async Task LogSecurityEventAsync(string eventType, string message,
            Dictionary<string, object>? properties = null)
        {
            await LogAsync(new LogEntry
            {
                Level = (int)LogLevel.Security,
                Category = "Security",
                Message = $"[{eventType}] {message}",
                Properties = properties
            });
        }

        public async Task LogAnalyticsAsync(string eventName,
            Dictionary<string, object>? properties = null)
        {
            await LogAsync(new LogEntry
            {
                Level = (int)LogLevel.Analytics,
                Category = "Analytics",
                Message = eventName,
                Properties = properties
            });
        }

        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 SendBatchAsync(batch);
                }
            }
            finally
            {
                _flushSemaphore.Release();
            }
        }

        private async Task SendBatchAsync(List<LogEntry> entries)
        {
            try
            {
                // Map EF entities to DTOs for API
                var dtos = entries.Select(e => new LogEntryDto
                {
                    Id = e.Id,
                    Timestamp = e.Timestamp,
                    ApplicationName = e.ApplicationName,
                    ApplicationVersion = e.ApplicationVersion,
                    Environment = e.Environment,
                    MachineName = e.MachineName,
                    Level = e.Level,
                    Category = e.Category,
                    Message = e.Message,
                    Exception = e.Exception,
                    StackTrace = e.StackTrace,
                    Properties = e.Properties,
                    UserId = e.UserId,
                    SessionId = e.SessionId,
                    RequestId = e.RequestId,
                    CorrelationId = e.CorrelationId
                }).ToList();

                var response = await _httpClient.PostAsJsonAsync("/api/logs/batch", dtos);
                response.EnsureSuccessStatusCode();
            }
            catch (Exception ex)
            {
                if (_options.EnableFallbackLogging)
                {
                    Console.WriteLine($"[LogCentral] Failed to send logs: {ex.Message}");
                }

                foreach (var entry in entries)
                {
                    _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);
        }
    }

    public class LogEntryDto
    {
        public string Id { get; set; } = Guid.NewGuid().ToString();
        public DateTime Timestamp { get; set; }
        public string ApplicationName { get; set; } = default!;
        public string ApplicationVersion { get; set; } = default!;
        public string Environment { get; set; } = default!;
        public string MachineName { get; set; } = default!;
        public int Level { get; set; }
        public string Category { get; set; } = default!;
        public string Message { get; set; } = default!;
        public string? Exception { get; set; }
        public string? StackTrace { get; set; }
        public Dictionary<string, object>? Properties { get; set; }
        public string? UserId { get; set; }
        public string? SessionId { get; set; }
        public string? RequestId { get; set; }
        public string? CorrelationId { get; set; }
    }

}
