using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using EonaCat.Json;
using EonaCat.Logger.Extensions;
using EonaCat.Logger.GrayLog;
using EonaCat.Logger.Syslog;
using Microsoft.Extensions.Logging;

// 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.Logger.Managers
{
    public class ErrorMessage
    {
        public Exception Exception { get; set; }
        public string Message { get; set; }
    }

    internal static class LogHelper
    {
        internal static event EventHandler<ErrorMessage> OnException;

        internal static string FormatMessageWithHeader(LoggerSettings settings, ELogType logType, string currentMessage, DateTime dateTime)
        {
            if (string.IsNullOrWhiteSpace(currentMessage))
                return currentMessage;

            StringBuilder sb = new StringBuilder();

            sb.Append(settings?.HeaderFormat ?? "[EonaCatLogger]");

            if (settings != null)
            {
                if (sb.ToString().Contains("{ts}"))
                    sb.Replace("{ts}", dateTime.ToString(settings.TimestampFormat) + " " + (settings.UseLocalTime ? "[LOCAL]" : "[UTC]"));

                if (sb.ToString().Contains("{host}"))
                    sb.Replace("{host}", $"[Host:{Dns.GetHostName()}]");

                if (sb.ToString().Contains("{thread}"))
                    sb.Replace("{thread}", $"[ThreadId:{Environment.CurrentManagedThreadId.ToString()}]");

                if (sb.ToString().Contains("{sev}"))
                    sb.Replace("{sev}", $"[{logType.ToString()}]");

                if (!settings.RemoveMessagePrefix && !currentMessage.Contains("[EonaCatLogger]"))
                    sb.Insert(0, "[EonaCatLogger] ");
            }

            sb.Append(" ");
            sb.Append(currentMessage);

            return sb.ToString();
        }



        internal static void SendToConsole(LoggerSettings settings, ELogType logType, string message, bool writeToConsole)
        {
            if (settings == null || !writeToConsole || string.IsNullOrWhiteSpace(message))
                return;

            if (settings.EnableColors && settings.Colors != null)
            {
                ConsoleColor prevForeground = Console.ForegroundColor;
                ConsoleColor prevBackground = Console.BackgroundColor;

                ConsoleColor foregroundColor;
                ConsoleColor backgroundColor;
                switch (logType)
                {
                    case ELogType.DEBUG:
                        foregroundColor = settings.Colors.Debug.Foreground;
                        backgroundColor = settings.Colors.Debug.Background;
                        break;
                    case ELogType.INFO:
                        foregroundColor = settings.Colors.Info.Foreground;
                        backgroundColor = settings.Colors.Info.Background;
                        break;
                    case ELogType.WARNING:
                        foregroundColor = settings.Colors.Warning.Foreground;
                        backgroundColor = settings.Colors.Warning.Background;
                        break;
                    case ELogType.ERROR:
                        foregroundColor = settings.Colors.Error.Foreground;
                        backgroundColor = settings.Colors.Error.Background;
                        break;
                    case ELogType.TRAFFIC:
                        foregroundColor = settings.Colors.Traffic.Foreground;
                        backgroundColor = settings.Colors.Traffic.Background;
                        break;
                    case ELogType.CRITICAL:
                        foregroundColor = settings.Colors.Critical.Foreground;
                        backgroundColor = settings.Colors.Critical.Background;
                        break;
                    case ELogType.TRACE:
                        foregroundColor = settings.Colors.Trace.Foreground;
                        backgroundColor = settings.Colors.Trace.Background;
                        break;
                    default:
                        return;
                }

                Console.ForegroundColor = foregroundColor;
                Console.BackgroundColor = backgroundColor;
                Console.WriteLine(message);
                Console.ForegroundColor = prevForeground;
                Console.BackgroundColor = prevBackground;
            }
            else
            {
                Console.WriteLine(message);
            }
        }

        internal static void SendToFile(ILogger logger, LoggerSettings settings, ELogType logType, string message)
        {
            if (logger == null || settings == null || !settings.EnableFileLogging || string.IsNullOrWhiteSpace(message))
                return;

            LogLevel logLevel = logType.ToLogLevel();

            if (logLevel >= settings.MaxLogType.ToLogLevel()) // Filter out log levels
                Log(logger, logLevel, message);
        }

        private static void Log(ILogger logger, LogLevel logLevel, string message)
        {
            logger.Log(logLevel, message);
        }

        public static async Task SendToSplunkServersAsync(LoggerSettings settings, string jsonPayload, bool sendToSplunkServer)
        {
            if (settings == null || !sendToSplunkServer || string.IsNullOrWhiteSpace(jsonPayload))
                return;

            if (settings.SplunkServers == null)
            {
                settings.SplunkServers = new List<SplunkServer.SplunkServer>();
            }

            foreach (var splunkServer in settings.SplunkServers)
            {
                if (!splunkServer.HasHecUrl)
                {
                    Console.WriteLine("Splunk server HecUrl not specified, skipping splunkServer");
                    continue;
                }

                if (!splunkServer.HasHecToken)
                {
                    OnException?.Invoke(null, new ErrorMessage { Message = $"Splunk server HecToken not specified, skipping splunkServer '{splunkServer.SplunkHecUrl}'" });
                    Console.WriteLine($"Splunk server HecToken not specified, skipping splunkServer '{splunkServer.SplunkHecUrl}'");
                    continue;
                }

                await Task.Run(async () =>
                {
                    try
                    {
                        using (var httpClient = new HttpClient(splunkServer.SplunkClientHandler))
                        {
                            var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
                            httpClient.DefaultRequestHeaders.Add("Authorization", $"Splunk {splunkServer.SplunkHecToken}");

                            var response = await httpClient.PostAsync(splunkServer.SplunkHecUrl, content);

                            if (!response.IsSuccessStatusCode)
                            {
                                OnException?.Invoke(null, new ErrorMessage { Message = $"Failed to send log to Splunk '{splunkServer.SplunkHecUrl}'. Status code: {response.StatusCode}" });
                            }
                        }
                    }
                    catch (Exception exception)
                    {
                        OnException?.Invoke(null, new ErrorMessage { Exception = exception, Message = $"Error while logging to Splunk Server '{splunkServer.SplunkHecUrl}': {exception.Message}" });
                    }
                });
            }
        }

        public static async Task SendToSplunkServersAsync(LoggerSettings settings, string logType, string message, bool sendToSplunkServer)
        {
            if (settings == null || !sendToSplunkServer || string.IsNullOrWhiteSpace(message))
                return;

            var jsonPayload = JsonHelper.ToJson(new
            {
                @event = message,
                sourcetype = logType
            });
            await SendToSplunkServersAsync(settings, jsonPayload, sendToSplunkServer);
        }

        internal static async Task SendToGrayLogServersAsync(LoggerSettings settings, string message, ELogType logLevel, string facility, string source, bool sendToGrayLogServer, string version = "1.1")
        {
            if (settings == null || !sendToGrayLogServer || string.IsNullOrWhiteSpace(message))
                return;

            if (settings.GrayLogServers == null || !settings.GrayLogServers.Any())
            {
                settings.GrayLogServers = new List<GrayLogServer> { new GrayLogServer("127.0.0.1", 12201) };
            }

            foreach (var grayLogServer in settings.GrayLogServers)
            {
                await Task.Run(async () =>
                {
                    try
                    {
                        var gelfMessage = new
                        {
                            version,
                            host = Environment.MachineName,
                            short_message = message,
                            level = logLevel.ToGrayLogLevel(),
                            facility,
                            source,
                            timestamp = DateTime.UtcNow.ToUnixTimestamp(),
                        };

                        var messageBytes = Encoding.UTF8.GetBytes(JsonHelper.ToJson(gelfMessage));
                        await grayLogServer.Udp.SendAsync(messageBytes, messageBytes.Length, new IPEndPoint(IPAddress.Parse(grayLogServer.Hostname), grayLogServer.Port));
                    }
                    catch (Exception exception)
                    {
                        OnException?.Invoke(null, new ErrorMessage { Exception = exception, Message = $"Error while logging to GrayLog Server '{grayLogServer.Hostname}': {exception.Message}" });
                    }
                });
            }
        }

        internal static async Task SendToSysLogServersAsync(LoggerSettings settings, string message, bool sendToSyslogServers)
        {
            if (settings == null || !sendToSyslogServers || string.IsNullOrWhiteSpace(message))
                return;

            if (settings.SysLogServers == null || !settings.SysLogServers.Any())
            {
                settings.SysLogServers = new List<SyslogServer> { new SyslogServer("127.0.0.1", 514) };
            }

            byte[] data = Encoding.UTF8.GetBytes(message);

            foreach (SyslogServer server in settings.SysLogServers)
            {
                await Task.Run(() =>
                {
                    try
                    {
                        if (string.IsNullOrWhiteSpace(server.Hostname))
                        {
                            OnException?.Invoke(null, new ErrorMessage { Message = "Server hostname not specified, skipping SysLog Server" });
                            return;
                        }

                        if (server.Port < 0)
                        {
                            OnException?.Invoke(null, new ErrorMessage { Message = "Server port must be zero or greater, skipping SysLog Server" });
                            return;
                        }

                        server.Udp.Send(data, data.Length);
                    }
                    catch (Exception exception)
                    {
                        OnException?.Invoke(null, new ErrorMessage { Exception = exception, Message = $"Error while logging to SysLog Server '{server.Hostname}': {exception.Message}" });
                    }
                });
            }
        }
    }
}
