using log4net;
using log4net.Config;
using log4net.Repository;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Timers;
using System.Xml;
namespace Logs
{
    /// 
    /// 日志帮助类
    /// 
    public static class LogHelper
    {
        /// 
        /// 日志仓库
        /// 
        public static ILoggerRepository? LoggerRepository { get; set; }
        #region 写入日志
        private static ILog? GetLog(ILogType type, string flagKey)
        {
            if (LoggerRepository == null)
                return null;
            if (type.SubLogNames.ContainsKey(flagKey))
                return LogManager.GetLogger(LoggerRepository.Name, type.SubLogNames[flagKey]);
            string defaultKey = string.Empty;
            foreach (var key in type.SubLogNames.Keys)
            {
                defaultKey = key;
                break;
            }
            return LogManager.GetLogger(LoggerRepository.Name, type.SubLogNames[defaultKey]);
        }
        #region Info
        /// 
        /// 运行记录
        /// 
        /// 日志类型
        /// 消息
        /// 异常
        /// 日志子项索引Key
        public static void Info(this ILogType type, string message, Exception ex, string flagKey) => GetLog(type, flagKey.ToUpper())?.Info(GetLogMessage(message, ex));
        /// 
        /// 运行记录
        /// 
        /// 日志类型
        /// 消息
        /// 日志子项索引Key
        public static void Info(this ILogType type, string message, string flagKey) => type.Info(message, null, flagKey);
        /// 
        /// 运行记录
        /// 
        /// 日志类型
        /// 异常
        /// 日志子项索引Key
        public static void Info(this ILogType type, Exception ex, string flagKey) => type.Info(null, ex, flagKey);
        #endregion Info
        #region Error
        /// 
        /// 错误记录
        /// 
        /// 日志类型
        /// 消息
        /// 异常
        /// 日志子项索引Key
        public static void Error(this ILogType type, string message, Exception ex, string flagKey) => GetLog(type, flagKey.ToUpper())?.Error(GetLogMessage(message, ex));
        /// 
        /// 错误记录
        /// 
        /// 日志类型
        /// 消息
        /// 日志子项索引Key
        public static void Error(this ILogType type, string message, string flagKey) => type.Error(message, null, flagKey);
        /// 
        /// 错误记录
        /// 
        /// 日志类型
        /// 异常
        /// 日志子项索引Key
        public static void Error(this ILogType type, Exception ex, string flagKey) => type.Error(null, ex, flagKey);
        #endregion Error
        #region Warn
        /// 
        /// 警告
        /// 
        /// 日志类型
        /// 消息
        /// 异常
        /// 日志子项索引Key
        public static void Warn(this ILogType type, string message, Exception ex, string flagKey) => GetLog(type, flagKey.ToUpper())?.Warn(GetLogMessage(message, ex));
        /// 
        /// 警告
        /// 
        /// 日志类型
        /// 消息
        /// 日志子项索引Key
        public static void Warn(this ILogType type, string message, string flagKey) => type.Warn(message, null, flagKey);
        /// 
        /// 警告
        /// 
        /// 日志类型
        /// 异常
        /// 日志子项索引Key
        public static void Warn(this ILogType type, Exception ex, string flagKey) => type.Warn(null, ex, flagKey);
        #endregion Warn
        /// 
        /// 获取详细日志信息
        /// 
        /// 信息
        /// 异常
        /// 详细信息
        private static string GetLogMessage(string message, Exception ex)
        {
            if (string.IsNullOrEmpty(message) && ex == null)
                return "Unknown error.";
            if (string.IsNullOrEmpty(message) && ex != null)
                return $"[Type:{ex.GetType().Name}][StackTrace:{ex.StackTrace}][Message:{ex.Message.Replace("\r\n", " ")}]";
            if (!string.IsNullOrEmpty(message) && ex == null)
                return message;
            return $"[Message:{message}][Type:{ex.GetType().Name}][StackTrace:{ex.StackTrace}][Ex Message:{ex.Message.Replace("\r\n", " ")}]";
        }
        #endregion 写入日志
        #region 初始化日志
        private static string? _configPath = "Log";
        private static int _fileSize = 500;
        private static readonly List _initList = new List();
        private static readonly LogConfig _configModel = new LogConfig();
        private static bool _cleanSet = false;
        private static string _previousCleanDay = null;
        private static int _cleanDays = 30;
        private static Timer _logCleanTimer;
        /// 
        /// 设置配置信息
        /// 
        /// 配置信息
        public static void SetConfigInfo(LogConfig logConfig)
        {
            SetConfigPath(logConfig.LogPath);
            SetLogFileSize(logConfig.LogSize);
            SetLogCleanDays(logConfig.LogDays);
            SetConfigInfo(logConfig.Logs);
        }
        /// 
        /// 日志配置
        /// 
        /// 
        public static void SetConfigPath(string path)
        {
            _configPath = path;
        }
        /// 
        /// 设置日志层级关系
        /// 
        /// 配置信息
        public static void SetConfigInfo(List logConfigs)
        {
            _configModel.Logs = logConfigs;
        }
        /// 
        /// 设置日志项目文件数量和大小
        /// 
        /// 单文件大小(单位MB,最小10)
        public static void SetLogFileSize(int size)
        {
            if (size < 10)
                return;
            _fileSize = size;
        }
        /// 
        /// 设置日志清理周期
        /// 
        /// 清理周期
        public static void SetLogCleanDays(int days)
        {
            if (days < 7)
                return;
            _cleanDays = days;
        }
        /// 
        /// 初始化Log组件
        /// 
        /// 日志类型数组
        public static void Init(params ILogType[] types)
        {
            var addedTypes = types.Where(p => !_initList.Contains(p)).ToList();
            if (addedTypes.Count == 0)
                return;
            if (_configModel.Logs == null || _configModel.Logs.Count == 0)
                throw new Exception("Log init, config is null.");
            _configPath = _configModel.Logs.FirstOrDefault(p => !string.IsNullOrEmpty(p.FileName)).FileName;
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", null));
            XmlElement configuration = xmlDoc.CreateElement("configuration");
            XmlElement root = xmlDoc.CreateElement("log4net");
            foreach (var item in addedTypes)
            {
                _initList.Add(item);
                LogConfigModel config = _configModel.Logs.FirstOrDefault(p => p.Name == item.LogName);
                if (config is null)
                {
                    config = new LogConfigModel
                    {
                        Name = item.LogName,
                        FileName = _configPath,
                        SubLogNames = item.SubLogNames
                    };
                }
                else
                {
                    if (config.SubLogNames is null)
                        config.SubLogNames = new Dictionary();
                    foreach (var key in config.SubLogNames.Keys)
                    {
                        if (item.SubLogNames != null && item.SubLogNames.ContainsKey(key))
                            continue;
                        item.SubLogNames?.Add(key, config.SubLogNames[key]);
                    }
                    foreach (var key in item.SubLogNames.Keys)
                    {
                        if (config.SubLogNames.ContainsKey(key))
                            continue;
                        config.SubLogNames.Add(key, item.SubLogNames[key]);
                    }
                }
                if (item.SubLogNames == null || item.SubLogNames.Count == 0)
                    continue;
                CreateXml(xmlDoc, root, config);
            }
            configuration.AppendChild(root);
            xmlDoc.AppendChild(configuration);
            //InitRepository.LoggerRepository ??= LogManager.CreateRepository("NETCoreRepository");
            if (LoggerRepository == null)
                LoggerRepository = LogManager.CreateRepository("NETCoreRepository");
            XmlConfigurator.Configure(LoggerRepository, new StreamReader(new MemoryStream(Encoding.ASCII.GetBytes(xmlDoc.OuterXml))).BaseStream);
            xmlDoc.Save("./log4net.config");
            if (!_cleanSet)
            {
                _cleanSet = true;
                _previousCleanDay = DateTime.Now.ToString("yyyyMMdd");
                // clean log
                _logCleanTimer = new Timer(1000 * 60);
                _logCleanTimer.Elapsed += LogCleanTimer_Elapsed;
                _logCleanTimer.Enabled = true;
            }
        }
        /// 
        /// 创建XML节点
        /// 
        /// XML文档
        /// 
        /// 
        private static void CreateXml(XmlDocument xmlDoc, XmlElement root, LogConfigModel config)
        {
            foreach (var key in config.SubLogNames.Keys)
            {
                // 创建 Logger
                XmlElement logger = xmlDoc.CreateElement("logger");
                logger.SetAttribute("name", config.SubLogNames[key]);
                XmlElement level = xmlDoc.CreateElement("level");
                level.SetAttribute("value", "ALL");
                XmlElement appender_ref = xmlDoc.CreateElement("appender-ref");
                appender_ref.SetAttribute("ref", $"{config.SubLogNames[key]}Appender");
                logger.AppendChild(level);
                logger.AppendChild(appender_ref);
                // 创建 Appender
                XmlElement appender = xmlDoc.CreateElement("appender");
                appender.SetAttribute("name", $"{config.SubLogNames[key]}Appender");
                appender.SetAttribute("type", "log4net.Appender.RollingFileAppender");
                XmlElement param = xmlDoc.CreateElement("param");
                param.SetAttribute("name", "Encoding");
                param.SetAttribute("value", "utf-8");
                appender.AppendChild(param);
                XmlElement file = xmlDoc.CreateElement("file");
                file.SetAttribute("value", Path.Combine(config.FileName, config.Name));
                appender.AppendChild(file);
                XmlElement appendToFile = xmlDoc.CreateElement("appendToFile");
                appendToFile.SetAttribute("value", "true");
                appender.AppendChild(appendToFile);
                XmlElement rollingStyle = xmlDoc.CreateElement("rollingStyle");
                rollingStyle.SetAttribute("value", "Composite");
                appender.AppendChild(rollingStyle);
                XmlElement maxSizeRollBackups = xmlDoc.CreateElement("maxSizeRollBackups");
                maxSizeRollBackups.SetAttribute("value", "-1");
                appender.AppendChild(maxSizeRollBackups);
                XmlElement maximumFileSize = xmlDoc.CreateElement("maximumFileSize");
                maximumFileSize.SetAttribute("value", $"{_fileSize}MB");
                appender.AppendChild(maximumFileSize);
                XmlElement lockingModel = xmlDoc.CreateElement("lockingModel");
                lockingModel.SetAttribute("type", "log4net.Appender.FileAppender+MinimalLock");
                appender.AppendChild(lockingModel);
                XmlElement staticLogFileName = xmlDoc.CreateElement("staticLogFileName");
                staticLogFileName.SetAttribute("value", "false");
                appender.AppendChild(staticLogFileName);
                XmlElement DatePattern = xmlDoc.CreateElement("DatePattern");
                DatePattern.SetAttribute("value", $"/yyyyMMdd/'{config.SubLogNames[key]}.log'");
                appender.AppendChild(DatePattern);
                XmlElement layout = xmlDoc.CreateElement("layout");
                layout.SetAttribute("type", "log4net.Layout.PatternLayout");
                XmlElement conversionPattern = xmlDoc.CreateElement("conversionPattern");
                conversionPattern.SetAttribute("value", "%date || %5level || %logger || %message || %exception || end %newline");
                layout.AppendChild(conversionPattern);
                appender.AppendChild(layout);
                root.AppendChild(appender);
                root.AppendChild(logger);
            }
        }
        private static void LogCleanTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            try
            {
                if (_previousCleanDay.Equals(DateTime.Now.ToString("yyyyMMdd")))
                    return;
                _previousCleanDay = DateTime.Now.ToString("yyyyMMdd");
                if (!Directory.Exists(_configPath))
                    return;
                string[] typeDirs = Directory.GetDirectories(_configPath);
                for (int i = 0; i < typeDirs.Length; i++)
                {
                    string[] dateDirs = Directory.GetDirectories(typeDirs[i]);
                    for (int j = 0; j < dateDirs.Length; j++)
                    {
                        string dirName = dateDirs[j].Substring(dateDirs[j].LastIndexOf('\\') + 1);
                        if (Convert.ToInt32(dirName) < Convert.ToInt32(DateTime.Now.AddDays(_cleanDays * -1).ToString("yyyyMMdd")))
                            Directory.Delete(dateDirs[j], true);
                    }
                }
            }
            catch (Exception ex)
            {
                //LogInfo.Log.Info(ex.Message, ex, "FATAL");
            }
        }
        #endregion 初始化日志
    }
}