using EonaCat.Logger.Syslog;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using EonaCat.Logger.EonaCatCoreLogger;
using EonaCat.Logger.EonaCatCoreLogger.Extensions;
using EonaCat.Logger.EonaCatCoreLogger.Models;
using EonaCat.Logger.Extensions;

namespace EonaCat.Logger.Managers
{
    // 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.

    public partial class LogManager : ILogManager, IDisposable
    {
        public event EventHandler<ErrorMessage> OnException;
        private DateTime CurrentDateTme => Settings.UseLocalTime ? DateTime.Now : DateTime.UtcNow;
        private DateTime _logDate;
        public ILoggerProvider LoggerProvider { get; private set; }
        public ILoggerFactory LoggerFactory { get; private set; }
        public ILogger Logger { get; private set; }
        public string CurrentLogFile => LoggerProvider is FileLoggerProvider fileLoggerProvider ? fileLoggerProvider.LogFile : string.Empty;

        public bool IsRunning { get; private set; }

        private static LogManager _instance;

        private readonly CancellationTokenSource _tokenSource = new CancellationTokenSource();

        public static LogManager Instance => InstanceInit();

        public LoggerSettings Settings { get; set; } = CreateDefaultSettings();

        private static LogManager InstanceInit()
        {
            if (_instance == null)
            {
                _instance = new LogManager(CreateDefaultSettings());
            }
            return _instance;
        }

        private static LoggerSettings CreateDefaultSettings()
        {
            var settings = new LoggerSettings();
            settings.Id = "EonaCatLogger";
            settings.MaxLogType = ELogType.TRACE;
            return settings;
        }

        protected virtual async Task DisposeAsync(bool disposing)
        {
            if (disposing)
            {
                await StopLoggingAsync().ConfigureAwait(false);
            }
        }

        public async Task StartNewLogAsync()
        {
            if (_tokenSource.IsCancellationRequested)
                return;

            if (IsRunning && CurrentDateTme.Date > _logDate.Date)
                await StopLoggingAsync().ConfigureAwait(false);

            IsRunning = true;

            CreateLogger();

            Directory.CreateDirectory(Settings.FileLoggerOptions.LogDirectory);

            _logDate = CurrentDateTme;
        }

        private void CreateLogger()
        {
            IServiceCollection serviceCollection = new ServiceCollection();
            serviceCollection.AddLogging(builder => builder.SetMinimumLevel(Settings.MaxLogType.ToLogLevel()).AddEonaCatFileLogger(configuration =>
            {
                var fileLoggerOptions = Settings.FileLoggerOptions;
                configuration.MaxWriteTries = fileLoggerOptions.MaxWriteTries;
                configuration.RetainedFileCountLimit = fileLoggerOptions.RetainedFileCountLimit;
                configuration.FlushPeriod = fileLoggerOptions.FlushPeriod;
                configuration.IsEnabled = fileLoggerOptions.IsEnabled;
                configuration.BatchSize = fileLoggerOptions.BatchSize;
                configuration.FileSizeLimit = fileLoggerOptions.FileSizeLimit;
                configuration.LogDirectory = fileLoggerOptions.LogDirectory;
                configuration.FileNamePrefix = fileLoggerOptions.FileNamePrefix;
                configuration.MaxRolloverFiles = fileLoggerOptions.MaxRolloverFiles;
                configuration.UseLocalTime = Settings.UseLocalTime;
            }));

            var serviceProvider = serviceCollection.BuildServiceProvider();
            LoggerProvider = serviceProvider.GetService<ILoggerProvider>();
            LoggerFactory = serviceProvider.GetService<ILoggerFactory>();
            Logger = LoggerFactory.CreateLogger(Settings.Id);
        }

        private void InternalWriteAsync(DateTime dateTime, string message, ELogType logType = ELogType.INFO, bool? writeToConsole = null, bool? sendToSyslogServers = null, bool? sendToSplunkServers = null, string customSplunkSourceType = null, bool? sendToGrayLogServers = null, string grayLogFacility = null, string grayLogSource = null, string grayLogVersion = "1.1")
        {
            if (string.IsNullOrEmpty(message) || logType == ELogType.NONE || (int)logType >= (int)Settings.MaxLogType)
                return;

            var messageWithHeader = LogHelper.FormatMessageWithHeader(Settings, logType, message, dateTime);

            writeToConsole ??= Settings.EnableConsole;
            if (writeToConsole.Value)
            {
                LogHelper.SendToConsole(Settings, logType, messageWithHeader, true);
            }

            Task.Run(() => { LogHelper.SendToFile(Logger, Settings, logType, message); });

            sendToSyslogServers ??= Settings.SendToSyslogServers;
            if (sendToSyslogServers.Value)
            {
                Task.Run(async () =>
                {
                    await LogHelper.SendToSysLogServersAsync(Settings, messageWithHeader, true);
                });
            }

            sendToSplunkServers ??= Settings.SendToSplunkServers;
            if (sendToSplunkServers.Value)
            {
                Task.Run(async () =>
                {
                    await LogHelper.SendToSplunkServersAsync(Settings, customSplunkSourceType ?? logType.ToString(), messageWithHeader, true);
                });
            }

            sendToGrayLogServers ??= Settings.SendToGrayLogServers;
            if (sendToGrayLogServers.Value)
            {
                Task.Run(async () =>
                {
                    await LogHelper.SendToGrayLogServersAsync(Settings, messageWithHeader, logType, grayLogFacility, grayLogSource, true, grayLogVersion);
                });
            }

            var logMessage = new EonaCatLogMessage
            {
                DateTime = dateTime,
                Message = message,
                LogType = logType,
                Origin = string.IsNullOrWhiteSpace(Settings.LogOrigin) ? "LogManager" : Settings.LogOrigin
            };

            Settings.OnLogEvent(logMessage);
        }

        public void Reset() => Settings.ResetLogEvent();

        public LogManager(LoggerSettings settings, string serverIp, int serverPort)
        {
            if (string.IsNullOrEmpty(serverIp))
                throw new ArgumentNullException(nameof(serverIp));

            if (serverPort < 0)
                throw new ArgumentException("Server port must be zero or greater.");

            settings.SysLogServers = new List<SyslogServer>
            {
                new SyslogServer(serverIp, serverPort)
            };

            Settings = settings;
            SetupLogManager();
        }

        public LogManager(LoggerSettings settings)
        {
            Settings = settings;
            SetupFileLogger(settings, null, true);
            SetupLogManager();
            LogHelper.OnException += LogHelper_OnException;
        }

        private void LogHelper_OnException(object sender, ErrorMessage e)
        {
            OnException?.Invoke(sender, e);
        }

        private void SetupFileLogger(LoggerSettings settings = null, string logFolder = null, bool defaultPrefix = true)
        {
            if (settings == null)
                settings = Settings;

            if (!settings.EnableFileLogging)
                return;

            if (logFolder != null)
                settings.FileLoggerOptions.LogDirectory = logFolder;

            if (string.IsNullOrWhiteSpace(settings.FileLoggerOptions.FileNamePrefix))
                settings.FileLoggerOptions.FileNamePrefix = defaultPrefix ? "EonaCat" : string.Empty;
        }

        private void SetupLogManager()
        {
            AppDomain.CurrentDomain.ProcessExit += ProcessExit;
            Console.CancelKeyPress += Console_CancelKeyPress;
            _logDate = CurrentDateTme;
        }

        private void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
        {
            Dispose();
        }

        private void ProcessExit(object sender, EventArgs e)
        {
            Dispose();
        }

        private Task StopLoggingAsync()
        {
            IsRunning = false;
            InternalWriteAsync(CurrentDateTme, $"{DllInfo.ApplicationName} stopped.");
            return Task.Delay(500);
        }

        public void DeleteCurrentLogFile()
        {
            if (CurrentLogFile != null)
                File.Delete(CurrentLogFile);
        }

        public void Dispose()
        {
            DisposeAsync(true).GetAwaiter().GetResult();
            GC.SuppressFinalize(this);
        }

        public void Write(Exception exception, string module = null, string method = null, bool criticalException = false, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, string grayLogFacility = null, string grayLogSource = null, string grayLogVersion = "1.1")
        {
            if (exception == null)
                return;

            Write(exception.FormatExceptionToMessage(module, method), criticalException ? ELogType.CRITICAL : ELogType.ERROR, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogFacility, grayLogSource, grayLogVersion);
        }

        public void Write(string message, ELogType logType = ELogType.INFO, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, string grayLogFacility = null, string grayLogSource = null, string grayLogVersion = "1.1")
        {
            if (logType == ELogType.NONE)
                return;

            if (!IsRunning)
                StartNewLogAsync().ConfigureAwait(false);

            InternalWriteAsync(CurrentDateTme, message, logType, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogFacility, grayLogSource, grayLogVersion);
        }
    }
}
