using Serilog;
using Serilog.Enrichers.Span; // Nuget: Serilog.Enrichers.Span
using Serilog.Events;
using Serilog.Exceptions;     // Nuget: Serilog.Exceptions
using Serilog.Exceptions.Core;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace Ayay.SerilogLogs
{
    /// <summary>
    /// 日志引导程序
    /// <para>负责初始化 Serilog 全局配置，包括文件策略、控制台输出、Seq 连接以及上下文丰富化。</para>
    /// <para>实现了按日期分文件夹、按模块分文件、以及主次日志分离的复杂策略。</para>
    /// </summary>
    public static class LogBootstrapper
    {
        /// <summary>
        /// 初始化日志系统 (通常在程序启动最开始调用)
        /// </summary>
        /// <param name="opts">配置选项</param>
        public static void Init(LogOptions opts)
        {
            // --------------------------------------------------------
            // 1. 目录容错处理 (防止因为 D 盘不存在导致程序崩溃)
            // --------------------------------------------------------
            string finalLogPath = opts.LogRootPath;
            try
            {
                if (!Directory.Exists(finalLogPath))
                {
                    Directory.CreateDirectory(finalLogPath);
                }
            }
            catch (Exception)
            {
                // 如果创建失败（比如没有 D 盘权限），回退到程序运行目录下的 Logs 文件夹
                finalLogPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
            }

            // --------------------------------------------------------
            // 2. 构建 Serilog 配置
            // --------------------------------------------------------
            var builder = new LoggerConfiguration();

            // 2.1 设置全局最低门槛 (兜底策略)
            builder.MinimumLevel.Is(opts.GlobalMinimumLevel);

            // 2.2 应用模块级别的特殊配置 (关键：处理 Algorithm=Debug, Ping=Fatal 等)
            if (opts.ModuleLevels != null)
            {
                foreach (var module in opts.ModuleLevels)
                {
                    builder.MinimumLevel.Override(module.Key, module.Value);
                }
            }
            // 强制覆盖微软自带的啰嗦日志
            builder.MinimumLevel.Override("Microsoft", LogEventLevel.Warning);
            builder.MinimumLevel.Override("System", LogEventLevel.Warning);
            builder.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Warning);
            
            // 2.3 注入全套元数据 (Enrichers) - 让日志更聪明
            builder
                // 注入全套元数据 (Enrichers) - 让日志更聪明
                .Enrich.FromLogContext()                  // 允许使用 .ForContext() 注入上下文
                .Enrich.WithProperty("AppId", opts.AppId) // 注入应用标识
                .Enrich.WithProperty("PcCode", opts.PcCode) // 注入应用标识
                .Enrich.WithMachineName()                 // [环境] 区分是哪台工控机 (建议加上)
                .Enrich.WithThreadId()                    // 线程ID
                .Enrich.WithProcessId()                   // 进程ID (用于识别重启)
                .Enrich.WithExceptionDetails()            // 结构化异常堆栈
                                                          // [异常] 结构化异常拆解 (非常强大)
                                                          // 它能把 ex.Data 和 InnerException 自动转成 JSON，而不是单纯的一堆字符串
                .Enrich.WithExceptionDetails(new DestructuringOptionsBuilder()
                .WithDefaultDestructurers())
                //.WithDestructurers(new[] { new SqlExceptionDestructurer() })) // 如果有数据库操作，这行很关键
                .Enrich.WithSpan();                       // 全链路追踪 ID

            // --------------------------------------------------------
            // 3. 配置输出端 (Sinks)
            // --------------------------------------------------------

            // 定义通用模板：包含了 SourceContext (模块名), TraceId, AppId
            // 示例: 2026-01-15 12:00:01 [INF] [Algorithm] [Dev01] 计算完成
            string outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}";

            // 3.1 控制台输出 (开发调试用)
            builder.WriteTo.Async(a => a.Console(
                restrictedToMinimumLevel: opts.ConsoleLevel,
                outputTemplate: outputTemplate
            ));

            // =================================================================
            // 3.2 [核心逻辑] 双重 Map 实现: 日期文件夹 -> 模块文件
            // =================================================================

            // 第一层 Map：根据【日期】分发 (key = "2026-01-15")
            // 目的：实现 D:\Logs\App\2026-01-15\ 这样的目录结构
            builder.WriteTo.Map(le => le.Timestamp.ToString("yyyy-MM-dd"), (dateKey, dailyConfig) =>
            {
                // 动态计算当天的文件夹路径
                var dailyPath = Path.Combine(finalLogPath, dateKey);

                // 第二层 Map：根据【模块 SourceContext】分发
                // 目的：在日期文件夹下，区分 System.txt, Network.txt
                dailyConfig.Map("SourceContext", (moduleKey, moduleConfig) =>
                {
                    // 如果没填 SourceContext，默认归为 General
                    var moduleName = string.IsNullOrEmpty(moduleKey) ? "General" : moduleKey;

                    // --- A. 配置【主要数据】文件 (Main) ---
                    // 规则: 只存 Information 及以上
                    // 路径: .../2026-01-15/System-20260115.txt
                    moduleConfig.File(
                        path: Path.Combine(dailyPath, $"{moduleName}-.txt"), // RollingInterval.Day 会自动把 - 替换为 -yyyyMMdd
                        restrictedToMinimumLevel: LogEventLevel.Information, // 👈 过滤掉 Debug
                        rollingInterval: RollingInterval.Day,

                        // 文件大小限制 (超过 10MB 切割 Main_001.txt)
                        fileSizeLimitBytes: opts.FileSizeLimitBytes,
                        rollOnFileSizeLimit: opts.RollOnFileSizeLimit,

                        // ⚠️ 设为 null，因为我们有自定义的 LogCleaner 接管清理工作，避免 Serilog 内部逻辑冲突
                        retainedFileCountLimit: null,

                        encoding: Encoding.UTF8,
                        outputTemplate: outputTemplate,
                        shared: true // 允许跨进程/多线程共享
                    );

                    // --- B. 配置【细节数据】文件 (Detail) ---
                    // 规则: 存 Debug 及以上 (包含 Main 的数据，是最全的备份)
                    // 路径: .../2026-01-15/SystemDetail-20260115.txt
                    moduleConfig.File(
                        path: Path.Combine(dailyPath, $"{moduleName}Detail-.txt"),
                        restrictedToMinimumLevel: LogEventLevel.Debug, // 👈 包含 Debug
                        rollingInterval: RollingInterval.Day,

                        // 文件大小限制 (与 Main 保持一致)
                        fileSizeLimitBytes: opts.FileSizeLimitBytes,
                        rollOnFileSizeLimit: opts.RollOnFileSizeLimit,

                        retainedFileCountLimit: null,

                        encoding: Encoding.UTF8,
                        outputTemplate: outputTemplate,
                        shared: true
                    );

                }, sinkMapCountLimit: 20); // 限制模块数量，防止 Context 乱填导致句柄爆炸

            }, sinkMapCountLimit: 2); // 限制日期 Sink 数量 (只需要保持今天和昨天，防止跨天运行时内存不释放)

            // 3.3 Seq 远程输出 (生产监控)
            if (!string.IsNullOrWhiteSpace(opts.SeqServerUrl))
            {
                builder.WriteTo.Async(a => a.Seq(
                    serverUrl: opts.SeqServerUrl,
                    apiKey: opts.SeqApiKey,
                    restrictedToMinimumLevel: opts.SeqLevel
                ));
            }

            // --------------------------------------------------------
            // 4. 生成 Logger 并赋值给全局静态变量
            // --------------------------------------------------------
            Log.Logger = builder.CreateLogger();

            // 支持 Emoji 显示
            Console.OutputEncoding = Encoding.UTF8;

            // --------------------------------------------------------
            // 5. 启动后台清理任务 (LogCleaner)
            // --------------------------------------------------------
            // 延迟 5 秒执行，避免在程序刚启动的高负载时刻争抢 IO 资源
            Task.Delay(5000).ContinueWith(_ =>
            {
                // 调用我们在 LogCleaner.cs 中定义的静态方法
                LogCleaner.RunAsync(
                    opts
                );
            });
        }

        /// <summary>
        /// 关闭日志系统，确保缓冲区数据写入磁盘/网络
        /// <para>请在程序退出 (OnExit) 时调用</para>
        /// </summary>
        public static void Close()
        {
            Log.CloseAndFlush();
        }
    }
}