package cc.winboll.studio.libappbase;

/**
 * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
 * @Date 2025/11/11 20:14
 * @Describe  * 应用全局崩溃处理类（单例逻辑）
 * 核心功能：捕获应用未捕获异常，记录崩溃日志到文件，启动崩溃报告页面，
 * 并通过「崩溃保险丝」机制防止重复崩溃，保障基础功能可用
 */
import android.app.Activity;
import android.app.Application;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public final class CrashHandler {

	/** 日志标签，用于当前类的日志输出标识 */
	public static final String TAG = "CrashHandler";

	/** 崩溃报告页面标题 */
	public static final String TITTLE = "CrashReport";

	/** Intent 传递崩溃信息的键（用于向崩溃页面传递日志） */
	public static final String EXTRA_CRASH_INFO = "crashInfo";

	/** SharedPreferences 存储键（用于记录崩溃状态） */
	final static String PREFS = CrashHandler.class.getName() + "PREFS";
	/** SharedPreferences 中存储「是否发生崩溃」的键 */
	final static String PREFS_CRASHHANDLER_ISCRASHHAPPEN = "PREFS_CRASHHANDLER_ISCRASHHAPPEN";

	/** 崩溃保险丝状态文件路径（存储当前熔断等级） */
	public static String _CrashCountFilePath;

	/** 系统默认的未捕获异常处理器（用于降级处理，避免 CrashHandler 自身崩溃） */
	public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();

	/**
	 * 初始化崩溃处理器（默认存储路径）
	 * 调用重载方法，崩溃日志默认存储在应用外部私有目录的 crash 文件夹下
	 * @param app 全局 Application 实例（用于获取存储目录、包信息等）
	 */
	public static void init(Application app) {
		// 初始化崩溃保险丝状态文件路径（外部存储/CrashHandler/IsCrashHandlerCrashHappen.dat）
		_CrashCountFilePath = app.getExternalFilesDir("CrashHandler") + "/IsCrashHandlerCrashHappen.dat";
		LogUtils.d(TAG, String.format("_CrashCountFilePath %s", _CrashCountFilePath));
		// 调用带目录参数的初始化方法，传入 null 使用默认路径
		init(app, null);
	}

	/**
	 * 初始化崩溃处理器（指定日志存储目录）
	 * 替换系统默认的未捕获异常处理器，自定义崩溃处理逻辑
	 * @param app 全局 Application 实例
	 * @param crashDir 崩溃日志存储目录（null 则使用默认路径）
	 */
	public static void init(final Application app, final String crashDir) {
		// 设置自定义未捕获异常处理器
		Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
				@Override
				public void uncaughtException(Thread thread, Throwable throwable) {
					try {
						// 尝试处理崩溃（捕获内部异常，避免 CrashHandler 自身崩溃）
						tryUncaughtException(thread, throwable);
					} catch (Throwable e) {
						e.printStackTrace();
						// 处理失败时，交给系统默认处理器兜底
						if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) {
							DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
						}
					}
				}

				/**
				 * 实际处理崩溃的核心方法
				 * 1. 熔断保险丝（记录崩溃次数）；2. 收集崩溃信息；3. 写入日志文件；4. 启动崩溃报告页面
				 * @param thread 发生崩溃的线程
				 * @param throwable 崩溃异常对象（包含堆栈信息）
				 */
				private void tryUncaughtException(Thread thread, Throwable throwable) {
					// 触发崩溃保险丝（每次崩溃熔断一次，降低防护等级）
					AppCrashSafetyWire.getInstance().burnSafetyWire();

					// 格式化崩溃发生时间（用于日志文件名和内容）
					final String time = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss", Locale.getDefault()).format(new Date());
					// 创建崩溃日志文件（默认路径：外部存储/crash/[时间].txt）
					File crashFile = new File(
						TextUtils.isEmpty(crashDir) ? new File(app.getExternalFilesDir(null), "crash") : new File(crashDir),
						"crash_" + time + ".txt"
					);

					// 获取应用版本信息（版本名、版本号）
					String versionName = "unknown";
					long versionCode = 0;
					try {
						PackageInfo packageInfo = app.getPackageManager().getPackageInfo(app.getPackageName(), 0);
						versionName = packageInfo.versionName;
						// 适配 Android 9.0+（API 28）的版本号获取方式
						versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() : packageInfo.versionCode;
					} catch (PackageManager.NameNotFoundException ignored) {}

					// 将异常堆栈信息转换为字符串
					String fullStackTrace;
					{
						StringWriter sw = new StringWriter();
						PrintWriter pw = new PrintWriter(sw);
						throwable.printStackTrace(pw); // 将异常堆栈写入 PrintWriter
						fullStackTrace = sw.toString();
						pw.close();
					}

					// 拼接崩溃信息（设备信息 + 应用信息 + 堆栈信息）
					StringBuilder sb = new StringBuilder();
					sb.append("************* Crash Head ****************\n");
					sb.append("Time Of Crash                        : ").append(time).append("\n");
					sb.append("Device Manufacturer                  : ").append(Build.MANUFACTURER).append("\n"); // 设备厂商
					sb.append("Device Model                         : ").append(Build.MODEL).append("\n"); // 设备型号
					sb.append("Android Version                      : ").append(Build.VERSION.RELEASE).append("\n"); // Android 版本
					sb.append("Android SDK                          : ").append(Build.VERSION.SDK_INT).append("\n"); // SDK 版本
					sb.append("App VersionName                      : ").append(versionName).append("\n"); // 应用版本名
					sb.append("App VersionCode                      : ").append(versionCode).append("\n"); // 应用版本号
					sb.append("************* Crash Head ****************\n");
					sb.append("\n").append(fullStackTrace); // 拼接异常堆栈

					final String errorLog = sb.toString();

					// 将崩溃日志写入文件（忽略写入失败）
					try {
						writeFile(crashFile, errorLog);
					} catch (IOException ignored) {}

					// 启动崩溃报告页面（标签用于代码块折叠）
					gotoCrashActiviy: {
						Intent intent = new Intent();
						LogUtils.d(TAG, "gotoCrashActiviy: ");

						// 根据保险丝状态选择启动的崩溃页面
						if (AppCrashSafetyWire.getInstance().isAppCrashSafetyWireOK()) {
							LogUtils.d(TAG, "gotoCrashActiviy: isAppCrashSafetyWireOK");
							// 保险丝正常：启动自定义样式的崩溃报告页面（GlobalCrashActivity）
							intent.setClass(app, GlobalCrashActivity.class);
							intent.putExtra(EXTRA_CRASH_INFO, errorLog); // 传递崩溃日志
						} else {
							LogUtils.d(TAG, "gotoCrashActiviy: else");
							// 保险丝熔断：启动基础版崩溃页面（CrashActivity，避免复杂页面再次崩溃）
							intent.setClass(app, CrashActivity.class);
							intent.putExtra(EXTRA_CRASH_INFO, errorLog);
						}

						// 设置意图标志：清除原有任务栈，创建新任务（避免回到崩溃前页面）
						intent.addFlags(
							Intent.FLAG_ACTIVITY_NEW_TASK
							| Intent.FLAG_ACTIVITY_CLEAR_TOP
							| Intent.FLAG_ACTIVITY_CLEAR_TASK
						);

						try {
							// 启动崩溃页面，终止当前进程（确保完全重启）
							app.startActivity(intent);
							android.os.Process.killProcess(android.os.Process.myPid());
							System.exit(0);
						} catch (ActivityNotFoundException e) {
							// 未找到崩溃页面（如未在 Manifest 注册），交给系统默认处理器
							e.printStackTrace();
							if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) {
								DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
							}
						} catch (Exception e) {
							// 其他异常，兜底处理
							e.printStackTrace();
							if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) {
								DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
							}
						}
					}
				}

				/**
				 * 将字符串内容写入文件（创建父目录、覆盖写入）
				 * @param file 目标文件（包含路径）
				 * @param content 待写入的内容（崩溃日志）
				 * @throws IOException 文件创建或写入失败时抛出
				 */
				private void writeFile(File file, String content) throws IOException {
					File parentFile = file.getParentFile();
					// 父目录不存在则创建
					if (parentFile != null && !parentFile.exists()) {
						parentFile.mkdirs();
					}
					file.createNewFile(); // 创建文件
					FileOutputStream fos = new FileOutputStream(file);
					fos.write(content.getBytes()); // 写入内容（默认 UTF-8 编码）
					try {
						fos.close(); // 关闭流
					} catch (IOException e) {}
				}
			});
	}

	/**
	 * 应用崩溃保险丝内部类（单例）
	 * 核心作用：限制短时间内重复崩溃，通过「熔断等级」控制崩溃页面启动策略
	 * 等级范围：MINI（1）~ MAX（2），每次崩溃等级-1，熔断后启动基础版崩溃页面
	 */
	public static final class AppCrashSafetyWire {

		/** 单例实例（volatile 保证多线程可见性） */
		private static volatile AppCrashSafetyWire _AppCrashSafetyWire;

		/** 当前熔断等级（1：最低防护；2：最高防护；≤0：熔断） */
		private volatile Integer currentSafetyLevel;
		/** 最低熔断等级（1，再崩溃则熔断） */
		private static final int _MINI = 1;
		/** 最高熔断等级（2，初始状态） */
		private static final int _MAX = 2;

		/**
		 * 私有构造方法（单例模式，禁止外部实例化）
		 * 初始化时加载本地存储的熔断等级
		 */
		private AppCrashSafetyWire() {
			LogUtils.d(TAG, "AppCrashSafetyWire()");
			currentSafetyLevel = loadCurrentSafetyLevel();
		}

		/**
		 * 获取单例实例（双重检查锁定，线程安全）
		 * @return AppCrashSafetyWire 单例
		 */
		public static synchronized AppCrashSafetyWire getInstance() {
			if (_AppCrashSafetyWire == null) {
				_AppCrashSafetyWire = new AppCrashSafetyWire();
			}
			return _AppCrashSafetyWire;
		}

		/**
		 * 设置当前熔断等级（内存中）
		 * @param currentSafetyLevel 目标等级（1~2）
		 */
		public void setCurrentSafetyLevel(int currentSafetyLevel) {
			this.currentSafetyLevel = currentSafetyLevel;
		}

		/**
		 * 获取当前熔断等级（内存中）
		 * @return 当前等级（1~2 或 null）
		 */
		public int getCurrentSafetyLevel() {
			return currentSafetyLevel;
		}

		/**
		 * 保存熔断等级到本地文件（持久化，重启应用生效）
		 * @param currentSafetyLevel 待保存的等级
		 */
		public void saveCurrentSafetyLevel(int currentSafetyLevel) {
			LogUtils.d(TAG, "saveCurrentSafetyLevel()");
			this.currentSafetyLevel = currentSafetyLevel;
			try {
				// 序列化等级到文件（ObjectOutputStream 写入 int）
				ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(_CrashCountFilePath));
				oos.writeInt(currentSafetyLevel);
				oos.flush();
				oos.close();
				LogUtils.d(TAG, String.format("saveCurrentSafetyLevel writeInt currentSafetyLevel %d", currentSafetyLevel));
			} catch (IOException e) {
				LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
			}
		}

		/**
		 * 从本地文件加载熔断等级（应用启动时初始化）
		 * @return 加载的等级（文件不存在则初始化为 MAX（2））
		 */
		public int loadCurrentSafetyLevel() {
			LogUtils.d(TAG, "loadCurrentSafetyLevel()");
			try {
				File f = new File(_CrashCountFilePath);
				if (f.exists()) {
					// 反序列化从文件读取等级
					ObjectInputStream ois = new ObjectInputStream(new FileInputStream(_CrashCountFilePath));
					currentSafetyLevel = ois.readInt();
					LogUtils.d(TAG, String.format("loadCurrentSafetyLevel() readInt currentSafetyLevel %d", currentSafetyLevel));
				} else {
					// 文件不存在，初始化等级为最高（2）并保存
					currentSafetyLevel = _MAX;
					LogUtils.d(TAG, String.format("loadCurrentSafetyLevel() currentSafetyLevel init to _MAX->%d", _MAX));
					saveCurrentSafetyLevel(currentSafetyLevel);
				}
			} catch (IOException e) {
				LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
			}
			return currentSafetyLevel;
		}

		/**
		 * 熔断保险丝（每次崩溃调用，降低防护等级）
		 * @return 熔断后是否仍在防护范围内（true：是；false：已熔断）
		 */
		boolean burnSafetyWire() {
			LogUtils.d(TAG, "burnSafetyWire()");
			// 加载当前等级
			int safeLevel = loadCurrentSafetyLevel();
			// 若在防护范围内（1~2），等级-1 并保存
			if (isSafetyWireWorking(safeLevel)) {
				LogUtils.d(TAG, "burnSafetyWire() use");
				saveCurrentSafetyLevel(safeLevel - 1);
				// 返回熔断后的状态
				return isSafetyWireWorking(safeLevel - 1);
			}
			return false;
		}

		/**
		 * 检查熔断等级是否在有效范围内（1~2）
		 * @param safetyLevel 待检查的等级
		 * @return true：在范围内（防护有效）；false：超出范围（已熔断）
		 */
		boolean isSafetyWireWorking(int safetyLevel) {
			LogUtils.d(TAG, "isSafetyWireOK()");
			LogUtils.d(TAG, String.format("SafetyLevel %d", safetyLevel));

			if (safetyLevel >= _MINI && safetyLevel <= _MAX) {
				LogUtils.d(TAG, String.format("In Safety Level"));
				return true;
			}
			LogUtils.d(TAG, String.format("Out of Safety Level"));
			return false;
		}

		/**
		 * 立即恢复熔断等级到最高（2）
		 * 用于重启应用后重置防护状态
		 */
		void resumeToMaximumImmediately() {
			LogUtils.d(TAG, "resumeToMaximumImmediately() call saveCurrentSafetyLevel(_MAX)");
			AppCrashSafetyWire.getInstance().saveCurrentSafetyLevel(_MAX);
		}

		/**
		 * 关闭防护（设置等级为最低（1））
		 * 下次崩溃直接熔断
		 */
		void off() {
			LogUtils.d(TAG, "off()");
			saveCurrentSafetyLevel(_MINI);
		}

		/**
		 * 检查当前保险丝是否有效（防护未熔断）
		 * @return true：有效（等级 1~2）；false：已熔断
		 */
		boolean isAppCrashSafetyWireOK() {
			LogUtils.d(TAG, "isAppCrashSafetyWireOK()");
			currentSafetyLevel = loadCurrentSafetyLevel();
			return isSafetyWireWorking(currentSafetyLevel);
		}

		/**
		 * 延迟恢复保险丝到最高等级（500ms 后）
		 * 核心作用：崩溃页面启动后，若下次即将熔断，提前恢复防护等级，避免持续崩溃
		 * @param context 上下文（用于获取主线程 Handler）
		 */
		void postResumeCrashSafetyWireHandler(final Context context) {
			// 主线程延迟 500ms 执行（避免页面启动时阻塞）
			new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
					@Override
					public void run() {
						LogUtils.d(TAG, "Handler run()");
						// 检查：若当前等级-1 后超出防护范围（即将熔断），则恢复到最高等级
						if (!AppCrashSafetyWire.getInstance().isSafetyWireWorking(currentSafetyLevel - 1)) {
							AppCrashSafetyWire.getInstance().resumeToMaximumImmediately();
							LogUtils.d(TAG, "postResumeCrashSafetyWireHandler: 恢复保险丝到最高等级");
						}
					}
				}, 500);
		}
	}

	/**
	 * 基础版崩溃报告页面（保险丝熔断时启动）
	 * 极简实现：仅展示崩溃日志，提供复制、重启功能，避免复杂布局导致二次崩溃
	 */
	public static final class CrashActivity extends Activity implements MenuItem.OnMenuItemClickListener {
		/** 菜单标识：复制崩溃日志 */
		private static final int MENUITEM_COPY = 0;
		/** 菜单标识：重启应用 */
		private static final int MENUITEM_RESTART = 1;

		/** 崩溃日志文本（从 CrashHandler 传递过来） */
		private String mLog;

		@Override
		protected void onCreate(Bundle savedInstanceState) {
			super.onCreate(savedInstanceState);
			// 初始化崩溃保险丝延迟恢复机制
			AppCrashSafetyWire.getInstance().postResumeCrashSafetyWireHandler(getApplicationContext());

			// 获取传递的崩溃日志
			mLog = getIntent().getStringExtra(EXTRA_CRASH_INFO);
			// 设置系统默认主题（避免自定义主题冲突）
			setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);

			// 动态创建布局（避免 XML 布局加载异常）
			setContentView: {
				// 垂直滚动视图（处理日志过长）
				ScrollView contentView = new ScrollView(this);
				contentView.setFillViewport(true);

				// 水平滚动视图（处理日志行过长）
				HorizontalScrollView hw = new HorizontalScrollView(this);
				hw.setBackgroundColor(Color.GRAY); // 背景色设为灰色

				// 日志显示文本框
				TextView message = new TextView(this);
				{
					int padding = dp2px(16); // 内边距 16dp（适配不同屏幕）
					message.setPadding(padding, padding, padding, padding);
					message.setText(mLog); // 设置崩溃日志
					message.setTextColor(Color.BLACK); // 文字黑色
					message.setTextIsSelectable(true); // 支持文本选择（便于手动复制）
				}

				// 组装布局：TextView -> HorizontalScrollView -> ScrollView
				hw.addView(message);
				contentView.addView(hw, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
				// 设置当前 Activity 布局
				setContentView(contentView);

				// 配置 ActionBar 标题和副标题
				getActionBar().setTitle(TITTLE);
				getActionBar().setSubtitle(GlobalApplication.class.getSimpleName() + " Error");
			}
		}

		/**
		 * 重写返回键逻辑：点击返回键直接重启应用
		 */
		@Override
		public void onBackPressed() {
			restart();
		}

		/**
		 * 重启当前应用（与 GlobalCrashActivity 逻辑一致）
		 * 清除任务栈，启动主 Activity，终止当前进程
		 */
		private void restart() {
			PackageManager pm = getPackageManager();
			// 获取应用启动意图（默认启动主 Activity）
			Intent intent = pm.getLaunchIntentForPackage(getPackageName());
			if (intent != null) {
				// 设置意图标志：清除原有任务栈，创建新任务
				intent.addFlags(
					Intent.FLAG_ACTIVITY_NEW_TASK
					| Intent.FLAG_ACTIVITY_CLEAR_TOP
					| Intent.FLAG_ACTIVITY_CLEAR_TASK
				);
				startActivity(intent);
			}
			// 关闭当前页面，终止进程，确保完全重启
			finish();
			android.os.Process.killProcess(android.os.Process.myPid());
			System.exit(0);
		}

		/**
		 * dp 转 px（适配不同屏幕密度）
		 * @param dpValue dp 值
		 * @return 转换后的 px 值
		 */
		private int dp2px(final float dpValue) {
			final float scale = Resources.getSystem().getDisplayMetrics().density;
			return (int) (dpValue * scale + 0.5f); // 四舍五入确保精度
		}

		/**
		 * 菜单点击事件回调（处理复制、重启）
		 * @param item 被点击的菜单项
		 * @return false：不消费事件（保持默认行为）
		 */
		@Override
		public boolean onMenuItemClick(MenuItem item) {
			switch (item.getItemId()) {
				case MENUITEM_COPY:
					// 复制日志到剪贴板
					ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
					cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
					Toast.makeText(getApplication(), "The text is copied.", Toast.LENGTH_SHORT).show();
					break;
				case MENUITEM_RESTART:
					// 恢复保险丝到最高等级，然后重启应用
					AppCrashSafetyWire.getInstance().resumeToMaximumImmediately();
					restart();
					break;
			}
			return false;
		}

		/**
		 * 创建 ActionBar 菜单（添加复制、重启项）
		 * @param menu 菜单容器
		 * @return true：显示菜单
		 */
		@Override
		public boolean onCreateOptionsMenu(Menu menu) {
			// 添加「复制」菜单：有空间时显示在 ActionBar，否则放入溢出菜单
			menu.add(0, MENUITEM_COPY, 0, "Copy")
				.setOnMenuItemClickListener(this)
				.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
			// 添加「重启」菜单：同上
			menu.add(0, MENUITEM_RESTART, 0, "Restart")
				.setOnMenuItemClickListener(this)
				.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
			return true;
		}
	}
}

