package cc.winboll.studio.libappbase;

import android.content.Context;
import dalvik.system.DexFile;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
 * @Date 2025/11/11 20:36
 * @Describe WinBoLl 应用日志管理工具类（单例逻辑）
 * 核心功能：日志分级控制、日志文件读写、TAG 过滤配置、应用内所有 TAG 自动扫描
 * 支持 Debug/Release 模式区分存储路径，日志持久化与清理
 * 适配 Java 7 语法，移除 Lambda、Stream 等 Java 8+ 特性
 */
public class LogUtils {

    /** 当前工具类的日志 TAG */
    public static final String TAG = "LogUtils";

    /**
     * 日志级别枚举（从低到高：关闭→错误→警告→信息→调试→详细）
     * 级别越高，输出的日志越详细
     */
    public static enum LOG_LEVEL { Off, Error, Warn, Info, Debug, Verbose }

    /** 是否初始化完成标志（volatile 保证多线程可见性） */
    private static volatile boolean sIsInited = false;
    /** 全局上下文（用于获取存储路径、包信息） */
    private static Context sContext;
    /** 日志时间格式化工具（格式：[yyyyMMdd_HHmmss_SSS]，精确到毫秒） */
    private static SimpleDateFormat sSimpleDateFormat = new SimpleDateFormat("[yyyyMMdd_HHmmss_SSS]", Locale.getDefault());
    /** 日志缓存文件夹（Debug 模式下存储在外部缓存，Release 存储在内部缓存） */
    private static File sLogCacheDir;
    /** 日志配置文件夹（存储 TAG 配置文件） */
    private static File sLogDataDir;
    /** 日志存储文件（所有日志写入此文件） */
    private static File sLogFile;
    /** 日志配置文件（存储日志级别、TAG 启用状态等配置） */
    private static File sLogConfigFile;
    /** 日志配置实体类（封装日志级别等配置） */
    private static LogUtilsBean sLogConfigBean;
    /** TAG 过滤映射表（key：TAG 名称；value：是否启用该 TAG 的日志输出） */
    public static Map<String, Boolean> sTagEnableMap = new HashMap<String, Boolean>();

    /**
     * 初始化日志工具（默认日志级别：Off，不输出日志）
     * @param context 全局上下文（建议传入 Application 实例）
     */
    public static void init(Context context) {
        sContext = context;
        init(context, LOG_LEVEL.Off);
    }

    /**
     * 初始化日志工具（指定日志级别）
     * 1. 根据 Debug/Release 模式初始化日志存储路径；
     * 2. 加载日志配置文件；
     * 3. 扫描应用内所有类的 TAG 并初始化过滤映射表；
     * 4. 标记初始化完成。
     * @param context 全局上下文
     * @param logLevel 初始日志级别
     */
    public static void init(Context context, LOG_LEVEL logLevel) {
        sContext = context;
        // 根据 Debug 模式选择存储路径（外部/内部存储）
        if (GlobalApplication.isDebugging()) {
            // Debug 模式：存储在外部缓存目录（可通过文件管理器查看）
            sLogCacheDir = new File(context.getApplicationContext().getExternalCacheDir(), TAG);
            sLogDataDir = context.getApplicationContext().getExternalFilesDir(TAG);
        } else {
            // Release 模式：存储在内部缓存目录（仅应用自身可访问）
            sLogCacheDir = new File(context.getApplicationContext().getCacheDir(), TAG);
            sLogDataDir = new File(context.getApplicationContext().getFilesDir(), TAG);
        }

        // 创建日志文件夹（不存在则创建）
        createDirIfNotExists(sLogCacheDir);
        createDirIfNotExists(sLogDataDir);

        // 初始化日志文件和配置文件路径
        sLogFile = new File(sLogCacheDir, "log.txt");
        sLogConfigFile = new File(sLogDataDir, TAG + ".json");

        // 加载日志配置（从文件读取，读取失败则创建默认配置）
        sLogConfigBean = LogUtilsBean.loadBeanFromFile(sLogConfigFile.getPath(), LogUtilsBean.class);
        if (sLogConfigBean == null) {
            sLogConfigBean = new LogUtilsBean();
            sLogConfigBean.setLogLevel(logLevel);
            // 保存默认配置到文件
            sLogConfigBean.saveBeanToFile(sLogConfigFile.getPath(), sLogConfigBean);
        }

        // 扫描应用内所有类的 TAG 并添加到过滤映射表
        scanAllClassTags();
        // 加载已保存的 TAG 启用状态配置
        loadTagEnableSettings();
        // 标记初始化完成
        sIsInited = true;

        // 打印初始化日志（调试用）
        d(TAG, String.format("TAG 过滤映射表初始化完成：%s", sTagEnableMap.toString()));
    }

    /**
     * 获取 TAG 过滤映射表（外部可通过此方法获取所有 TAG 及其启用状态）
     * @return TAG 名称与启用状态的映射
     */
    public static Map<String, Boolean> getTagEnableMap() {
        return sTagEnableMap;
    }

    /**
     * 加载已保存的 TAG 启用状态配置
     * 从 LogUtilsClassTAGBean 列表中读取每个 TAG 的启用状态，更新到映射表
     */
    private static void loadTagEnableSettings() {
        ArrayList<LogUtilsClassTAGBean> tagSettingList = new ArrayList<LogUtilsClassTAGBean>();
        // 从文件加载 TAG 配置列表
        LogUtilsClassTAGBean.loadBeanList(sContext, tagSettingList, LogUtilsClassTAGBean.class);

        // 遍历配置列表，更新 TAG 启用状态（Java 7 增强 for 循环）
        for (LogUtilsClassTAGBean tagSetting : tagSettingList) {
            String tag = tagSetting.getTag();
            boolean isEnable = tagSetting.getEnable();
            // 仅更新已存在的 TAG（避免无效配置）
            if (sTagEnableMap.containsKey(tag)) {
                sTagEnableMap.put(tag, isEnable);
            }
        }
    }

    /**
     * 保存当前 TAG 启用状态配置到文件
     * 将映射表中的 TAG 及其启用状态转换为 LogUtilsClassTAGBean 列表，持久化到文件
     */
    private static void saveTagEnableSettings() {
        ArrayList<LogUtilsClassTAGBean> tagSettingList = new ArrayList<LogUtilsClassTAGBean>();
        // 遍历映射表，构建配置列表（Java 7 迭代器遍历）
        Iterator<Map.Entry<String, Boolean>> iterator = sTagEnableMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Boolean> entry = iterator.next();
            tagSettingList.add(new LogUtilsClassTAGBean(entry.getKey(), entry.getValue()));
        }
        // 保存配置列表到文件
        LogUtilsClassTAGBean.saveBeanList(sContext, tagSettingList, LogUtilsClassTAGBean.class);
    }

    /**
     * 扫描应用内所有类的 TAG 并添加到过滤映射表
     * 1. 通过 DexFile 读取 APK 中所有类；
     * 2. 过滤指定包名前缀（cc.winboll.studio）的类；
     * 3. 反射获取类中 public static final String TAG 字段的值；
     * 4. 将 TAG 加入映射表，默认禁用（false）。
     */
    private static void scanAllClassTags() {
        try {
            // 应用 APK 路径（通过上下文获取）
            String apkPath = sContext.getPackageCodePath();
            d(TAG, String.format("APK 路径：%s", apkPath));

            // 读取 APK 中的所有类
            DexFile dexFile = new DexFile(apkPath);
            Enumeration<String> classNames = dexFile.entries();

            int totalClassCount = 0; // 总类数（调试用）
            List<String> targetClassNames = new ArrayList<String>(); // 目标包名下的类名列表
            String targetPackagePrefix = "cc.winboll.studio"; // 目标包名前缀

            // 过滤目标包名下的类（Java 7 枚举遍历）
            while (classNames.hasMoreElements()) {
                totalClassCount++;
                String className = classNames.nextElement();
                if (className.startsWith(targetPackagePrefix)) {
                    targetClassNames.add(className);
                }
            }

            // 打印扫描统计（调试用）
            d(TAG, String.format("APK 总类数：%d，目标包下类数：%d", totalClassCount, targetClassNames.size()));

            // 反射获取每个类的 TAG 字段（Java 7 增强 for 循环）
            for (String className : targetClassNames) {
                try {
                    Class<?> clazz = Class.forName(className);
                    // 获取类中所有声明的字段
                    Field[] fields = clazz.getDeclaredFields();
                    for (Field field : fields) {
                        // 过滤条件：public static String 类型，且字段名是 "TAG"
                        if (Modifier.isStatic(field.getModifiers())
							&& Modifier.isPublic(field.getModifiers())
							&& field.getType() == String.class
							&& "TAG".equals(field.getName())) {
                            // 获取 TAG 字段的值（静态字段，传入 null 即可）
                            String tagValue = (String) field.get(null);
                            // 添加到映射表，默认禁用
                            sTagEnableMap.put(tagValue, false);
                        }
                    }
                } catch (NoClassDefFoundError e) {
                    // 捕获反射异常，避免单个类扫描失败影响整体
                    d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
                } catch (ClassNotFoundException e) {
                    d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
                } catch (IllegalAccessException e) {
                    d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
                }
            }
        } catch (IOException e) {
            // 捕获 APK 读取异常
            d(TAG, e, Thread.currentThread().getStackTrace());
        }
    }

    /**
     * 设置单个 TAG 的启用状态
     * @param tag TAG 名称
     * @param isEnable 是否启用（true：输出该 TAG 的日志；false：不输出）
     */
    public static void setTagEnable(String tag, boolean isEnable) {
        // 遍历映射表，更新目标 TAG 的状态（Java 7 迭代器遍历）
        Iterator<Map.Entry<String, Boolean>> iterator = sTagEnableMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Boolean> entry = iterator.next();
            if (tag.equals(entry.getKey())) {
                entry.setValue(isEnable);
                break;
            }
        }
        // 保存配置到文件（持久化）
        saveTagEnableSettings();
        d(TAG, String.format("TAG 配置更新：%s", sTagEnableMap.toString()));
    }

    /**
     * 设置所有 TAG 的启用状态（批量控制）
     * @param isEnable 是否启用（true：所有 TAG 均输出日志；false：所有 TAG 均不输出）
     */
    public static void setAllTagsEnable(boolean isEnable) {
        // 遍历映射表，批量更新所有 TAG 的状态（Java 7 迭代器遍历）
        Iterator<Map.Entry<String, Boolean>> iterator = sTagEnableMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Boolean> entry = iterator.next();
            entry.setValue(isEnable);
        }
        // 保存配置到文件（持久化）
        saveTagEnableSettings();
        d(TAG, String.format("所有 TAG 配置更新：%s", sTagEnableMap.toString()));
    }

    /**
     * 设置全局日志级别（控制日志输出的详细程度）
     * @param logLevel 目标日志级别
     */
    public static void setLogLevel(LOG_LEVEL logLevel) {
        if (sLogConfigBean != null) {
            sLogConfigBean.setLogLevel(logLevel);
            // 保存配置到文件（持久化）
            sLogConfigBean.saveBeanToFile(sLogConfigFile.getPath(), sLogConfigBean);
        }
    }

    /**
     * 获取当前全局日志级别
     * @return 当前日志级别
     */
    public static LOG_LEVEL getLogLevel() {
        return sLogConfigBean != null ? sLogConfigBean.getLogLevel() : LOG_LEVEL.Off;
    }

    /**
     * 判断当前日志是否可输出（校验初始化状态、TAG 启用状态、日志级别）
     * @param tag 日志 TAG
     * @param logLevel 日志级别
     * @return true：可输出；false：不可输出
     */
    private static boolean isLoggable(String tag, LOG_LEVEL logLevel) {
        // 未初始化：不输出
        if (!sIsInited) {
            return false;
        }
        // TAG 未配置或未启用：不输出
        if (sTagEnableMap.get(tag) == null || !sTagEnableMap.get(tag)) {
            return false;
        }
        // 日志级别未达到：不输出
        if (!isLevelMatched(logLevel)) {
            return false;
        }
        return true;
    }

    /**
     * 判断日志级别是否匹配（当前全局级别 >= 目标级别时可输出）
     * 例：全局级别为 Debug（4），则 Error（1）、Warn（2）、Info（3）、Debug（4）均可输出
     * @param logLevel 目标日志级别
     * @return true：级别匹配；false：不匹配
     */
    private static boolean isLevelMatched(LOG_LEVEL logLevel) {
        if (sLogConfigBean == null) {
            return false;
        }
        // 枚举的 ordinal() 方法返回索引（Off=0，Error=1，...，Verbose=5）
        return sLogConfigBean.getLogLevel().ordinal() >= logLevel.ordinal();
    }

    /**
     * 获取日志缓存文件夹路径（外部可通过此方法获取日志存储目录）
     * @return 日志缓存文件夹
     */
    public static File getLogCacheDir() {
        return sLogCacheDir;
    }

    /**
     * 输出 Error 级别日志
     * @param tag TAG 名称
     * @param message 日志内容
     */
    public static void e(String tag, String message) {
        if (isLoggable(tag, LOG_LEVEL.Error)) {
            saveLog(tag, LOG_LEVEL.Error, message);
        }
    }

    /**
     * 输出 Error 级别日志（带异常信息和调用栈）
     * 错误级别专用，包含完整异常详情，便于错误定位和排查
     * @param tag TAG 名称
     * @param message 日志内容
     * @param e 异常对象（存储异常信息和调用栈）
     */
    public static void e(String tag, String message, Exception e) {
        if (isLoggable(tag, LOG_LEVEL.Error)) {
            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
            StringBuilder sb = new StringBuilder(message);
            // 拼接异常信息（类型 + 消息）
            sb.append(" \nException: ")
				.append(e.getClass().getSimpleName())
				.append(" : ")
				.append(e.getMessage() != null ? e.getMessage() : "无异常消息");
            // 拼接调用栈信息（stackTrace[2] 为实际调用处）
            sb.append(" \nAt ")
				.append(stackTrace[2].getMethodName())
				.append(" (")
				.append(stackTrace[2].getFileName())
				.append(":")
				.append(stackTrace[2].getLineNumber())
				.append(")");
            saveLog(tag, LOG_LEVEL.Error, sb.toString());
        }
    }

    /**
     * 输出 Warn 级别日志
     * @param tag TAG 名称
     * @param message 日志内容
     */
    public static void w(String tag, String message) {
        if (isLoggable(tag, LOG_LEVEL.Warn)) {
            saveLog(tag, LOG_LEVEL.Warn, message);
        }
    }

    /**
     * 输出 Warn 级别日志（带异常信息和调用栈）
     * 包含日志内容、异常详情、调用位置，便于警告场景下的问题定位
     * @param tag TAG 名称
     * @param message 日志内容
     * @param e 异常对象（存储异常信息和调用栈）
     */
    public static void w(String tag, String message, Exception e) {
        if (isLoggable(tag, LOG_LEVEL.Warn)) {
            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
            StringBuilder sb = new StringBuilder(message);
            // 拼接异常信息（类型 + 消息）
            sb.append(" \nException: ")
				.append(e.getClass().getSimpleName())
				.append(" : ")
				.append(e.getMessage() != null ? e.getMessage() : "无异常消息");
            // 拼接调用栈信息（stackTrace[2] 为实际调用处）
            sb.append(" \nAt ")
				.append(stackTrace[2].getMethodName())
				.append(" (")
				.append(stackTrace[2].getFileName())
				.append(":")
				.append(stackTrace[2].getLineNumber())
				.append(")");
            saveLog(tag, LOG_LEVEL.Warn, sb.toString());
        }
    }

    /**
     * 输出 Info 级别日志
     * @param tag TAG 名称
     * @param message 日志内容
     */
    public static void i(String tag, String message) {
        if (isLoggable(tag, LOG_LEVEL.Info)) {
            saveLog(tag, LOG_LEVEL.Info, message);
        }
    }

    /**
     * 输出 Debug 级别日志（基础版）
     * @param tag TAG 名称
     * @param message 日志内容
     */
    public static void d(String tag, String message) {
        if (isLoggable(tag, LOG_LEVEL.Debug)) {
            saveLog(tag, LOG_LEVEL.Debug, message);
        }
    }

    /**
     * 输出 Debug 级别日志（带调用栈信息）
     * 包含调用方法名、文件名、行号，便于调试定位
     * @param tag TAG 名称
     * @param message 日志内容
     * @param stackTrace 线程调用栈（通常传入 Thread.currentThread().getStackTrace()）
     */
    public static void d(String tag, String message, StackTraceElement[] stackTrace) {
        if (isLoggable(tag, LOG_LEVEL.Debug)) {
            StringBuilder sb = new StringBuilder(message);
            // 拼接调用栈信息（stackTrace[2] 为实际调用处）
            sb.append(" \nAt ")
				.append(stackTrace[2].getMethodName())
				.append(" (")
				.append(stackTrace[2].getFileName())
				.append(":")
				.append(stackTrace[2].getLineNumber())
				.append(")");
            saveLog(tag, LOG_LEVEL.Debug, sb.toString());
        }
    }

    /**
     * 输出 Debug 级别日志（带异常信息和调用栈）
     * 包含异常类型、异常信息、调用位置，便于异常定位
     * @param tag TAG 名称
     * @param e 异常对象
     * @param stackTrace 线程调用栈
     */
    public static void d(String tag, Exception e, StackTraceElement[] stackTrace) {
        if (isLoggable(tag, LOG_LEVEL.Debug)) {
            StringBuilder sb = new StringBuilder();
            // 拼接异常信息
            sb.append(e.getClass().getSimpleName())
				.append(" : ")
				.append(e.getMessage() != null ? e.getMessage() : "无异常消息")
				// 拼接调用栈信息
				.append(" \nAt ")
				.append(stackTrace[2].getMethodName())
				.append(" (")
				.append(stackTrace[2].getFileName())
				.append(":")
				.append(stackTrace[2].getLineNumber())
				.append(")");
            saveLog(tag, LOG_LEVEL.Debug, sb.toString());
        }
    }

    /**
     * 输出 Debug 级别日志（带日志内容+异常对象，简化调用）
     * 无需手动传入调用栈，内部自动获取，适配常见调试场景
     * @param tag TAG 名称
     * @param message 日志内容
     * @param e 异常对象（存储异常信息和调用栈）
     */
    public static void d(String tag, String message, Exception e) {
        if (isLoggable(tag, LOG_LEVEL.Debug)) {
            // 自动获取当前线程调用栈，简化外部调用
            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
            StringBuilder sb = new StringBuilder(message);
            // 拼接异常信息（类型 + 消息）
            sb.append(" \nException: ")
				.append(e.getClass().getSimpleName())
				.append(" : ")
				.append(e.getMessage() != null ? e.getMessage() : "无异常消息");
            // 拼接调用栈信息（stackTrace[2] 为实际调用处）
            sb.append(" \nAt ")
				.append(stackTrace[2].getMethodName())
				.append(" (")
				.append(stackTrace[2].getFileName())
				.append(":")
				.append(stackTrace[2].getLineNumber())
				.append(")");
            saveLog(tag, LOG_LEVEL.Debug, sb.toString());
        }
    }

    /**
     * 输出 Verbose 级别日志（最详细级别）
     * @param tag TAG 名称
     * @param message 日志内容
     */
    public static void v(String tag, String message) {
        if (isLoggable(tag, LOG_LEVEL.Verbose)) {
            saveLog(tag, LOG_LEVEL.Verbose, message);
        }
    }

    /**
     * 核心日志保存方法（将日志写入文件）
     * 日志格式：[级别]  [时间戳]  [TAG]
     *          日志内容
     * @param tag TAG 名称
     * @param logLevel 日志级别
     * @param message 日志内容
     */
    private static void saveLog(String tag, LOG_LEVEL logLevel, String message) {
        BufferedWriter writer = null;
        try {
            // 以追加模式打开日志文件，UTF-8 编码
            writer = new BufferedWriter(
                new OutputStreamWriter(
                    new FileOutputStream(sLogFile, true), 
                    "UTF-8"
                )
            );
            // 拼接日志内容（级别 + 时间 + TAG + 消息）
            String logContent = String.format(
                "[%s]  %s  [%s]\n%s\n",
                logLevel.name(),          // 日志级别（如 Debug）
                sSimpleDateFormat.format(System.currentTimeMillis()), // 时间戳
                tag,                      // TAG 名称
                message                   // 日志内容
            );
            // 写入文件
            writer.write(logContent);
        } catch (IOException e) {
            // 日志写入失败时，输出内部调试日志
            d(TAG, "日志写入失败：" + (e.getMessage() != null ? e.getMessage() : "未知错误"));
        } finally {
            // 关闭流，避免资源泄漏（Java 7 手动关闭，不使用 try-with-resources）
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 加载历史日志（读取日志文件所有内容）
     * @return 历史日志字符串（空字符串表示文件不存在或读取失败）
     */
    public static String loadLog() {
        // 日志文件不存在，返回空
        if (sLogFile == null || !sLogFile.exists()) {
            return "";
        }

        StringBuilder logContent = new StringBuilder();
        BufferedReader reader = null;
        try {
            // 以 UTF-8 编码读取日志文件
            reader = new BufferedReader(
                new InputStreamReader(
                    new FileInputStream(sLogFile), 
                    "UTF-8"
                )
            );
            String line;
            // 逐行读取并拼接（Java 7 普通 while 循环）
            while ((line = reader.readLine()) != null) {
                logContent.append(line).append("\n");
            }
        } catch (IOException e) {
            // 读取失败时，输出内部调试日志
            d(TAG, "日志读取失败：" + (e.getMessage() != null ? e.getMessage() : "未知错误"));
        } finally {
            // 关闭流，避免资源泄漏
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return logContent.toString();
    }

    /**
     * 清理历史日志（清空日志文件内容）
     */
    public static void cleanLog() {
        if (sLogFile == null || !sLogFile.exists()) {
            return;
        }

        try {
            // 写入空字符串到文件，实现清空（Java 7 手动处理流）
            BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(
                    new FileOutputStream(sLogFile), 
                    "UTF-8"
                )
            );
            writer.write("");
            writer.close();
        } catch (IOException e) {
            // 清空失败时，输出内部调试日志（带调用栈）
            d(TAG, e, Thread.currentThread().getStackTrace());
        }
    }

    /**
     * 辅助方法：创建文件夹（不存在则创建）
     * @param dir 目标文件夹
     */
    private static void createDirIfNotExists(File dir) {
        if (dir != null && !dir.exists()) {
            dir.mkdirs();
        }
    }
}

