package org.xyzh.system.log;

import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.xyzh.common.utils.StringUtils;
import org.xyzh.common.utils.spring.SpringContextUtil;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
/**
 * @description 数据库日志Appender - 异步写入数据库
 * @filename DatabaseAppender.java
 * @author yslg
 * @copyright xyzh
 * @since 2025-10-30
 */
@Plugin(name = "DatabaseAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
public class DatabaseAppender extends AbstractAppender {

    // 异步队列，存储待处理的日志事件上下文
    private static final BlockingQueue<LogEventContext> logQueue = new ArrayBlockingQueue<>(10000);
    
    // 日志处理工作线程
    private static LogEventProcessor processor;
    
    protected DatabaseAppender(String name, Filter filter, boolean ignoreExceptions) {
        super(name, filter, null, ignoreExceptions, Property.EMPTY_ARRAY);
        
        // 启动日志处理线程
        if (processor == null) {
            processor = new LogEventProcessor();
            Thread processorThread = new Thread(processor, "log-db-processor");
            processorThread.setDaemon(true);
            processorThread.start();
        }
    }

    @Override
    public void append(LogEvent event) {
        // 在主线程中提取HTTP上下文信息（异步线程中无法获取）
        String userId = null;
        String username = null;
        String requestUrl = null;
        String requestMethod = null;
        String ipAddress = null;
        String ipSource = null;
        String browser = null;
        String os = null;
        
        try {
            // 尝试获取用户信息
            SpringContextUtil contextUtil = SpringContextUtil.getBean(SpringContextUtil.class);
            if (contextUtil != null) {
                Object loginUtil = SpringContextUtil.getBean("loginUtil");
                if (loginUtil != null) {
                    java.lang.reflect.Method getUserIdMethod = loginUtil.getClass().getMethod("getCurrentUserId");
                    java.lang.reflect.Method getUsernameMethod = loginUtil.getClass().getMethod("getCurrentUsername");
                    userId = (String) getUserIdMethod.invoke(null);
                    username = (String) getUsernameMethod.invoke(null);
                    if(username == null || username.trim().isEmpty()) {
                        username = "system";
                    }
                }
            }
        } catch (Exception e) {
            // 非登录状态或反射调用失败，忽略
        }
        
        try {
            // 尝试获取请求信息
            Class<?> servletUtilsClass = Class.forName("org.xyzh.common.utils.ServletUtils");
            requestUrl = (String) servletUtilsClass.getMethod("getRequestUrl").invoke(null);
            if(StringUtils.isNotBlank(requestUrl)){
                // 移除URL参数部分（使用indexOf避免正则表达式问题）
                int questionMarkIndex = requestUrl.indexOf('?');
                if(questionMarkIndex != -1) {
                    requestUrl = requestUrl.substring(0, questionMarkIndex);
                }
            }
            requestMethod = (String) servletUtilsClass.getMethod("getHeader", String.class).invoke(null, "X-HTTP-Method-Override");
            ipAddress = (String) servletUtilsClass.getMethod("getClientIp").invoke(null);
            ipSource = (String) servletUtilsClass.getMethod("getIpSource").invoke(null);
            browser = (String) servletUtilsClass.getMethod("getBrowser").invoke(null);
            os = (String) servletUtilsClass.getMethod("getOs").invoke(null);
        } catch (Exception e) {
            e.printStackTrace();
            // 非HTTP请求上下文或类不存在，忽略
        }
        
        // 创建日志事件上下文（包含HTTP上下文信息）
        LogEventContext context = new LogEventContext(
            event.toImmutable(), userId, username,
            requestUrl, requestMethod, ipAddress, ipSource, browser, os
        );
        
        // 将日志事件上下文添加到队列中，非阻塞方式
        if (!logQueue.offer(context)) {
            // 队列满了，记录警告但不阻塞应用
            System.err.println("日志队列已满，丢弃日志: " + event.getMessage().getFormattedMessage());
        }
    }

    @Override
    public void stop() {
        super.stop();
        if (processor != null) {
            processor.shutdown();
        }
    }

    /**
     * @description 创建 DatabaseAppender 实例的工厂方法
     */
    @PluginFactory
    public static DatabaseAppender createAppender(
            @PluginAttribute("name") String name,
            @PluginElement("Filter") Filter filter,
            @PluginAttribute("ignoreExceptions") boolean ignoreExceptions) {
        
        if (name == null) {
            LOGGER.error("未指定 DatabaseAppender 名称");
            return null;
        }
        
        return new DatabaseAppender(name, filter, ignoreExceptions);
    }

    /**
     * @description 获取日志队列，供处理器使用
     */
    public static BlockingQueue<LogEventContext> getLogQueue() {
        return logQueue;
    }

    /**
     * @description 日志事件处理器（内部类）
     */
    static class LogEventProcessor implements Runnable {
        private volatile boolean running = true;

        @Override
        public void run() {
            while (running) {
                try {
                    // 从队列中获取日志事件上下文（阻塞等待）
                    LogEventContext context = logQueue.poll(1, TimeUnit.SECONDS);
                    if (context != null) {
                        processLogEvent(context);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                } catch (Exception e) {
                    System.err.println("处理日志事件失败: " + e.getMessage());
                    e.printStackTrace();
                }
            }
        }

        /**
         * @description 处理单个日志事件
         */
        private void processLogEvent(LogEventContext context) {
            try {
                // 通过 Spring 容器获取日志Service并保存
                LogEventHandler handler = SpringContextUtil.getBean(LogEventHandler.class);
                if (handler != null) {
                    handler.handleLogEvent(context);
                }
            } catch (Exception e) {
                System.err.println("保存日志到数据库失败: " + e.getMessage());
            }
        }

        public void shutdown() {
            running = false;
        }
    }
}

