import errorManager from '@ohos.app.ability.errorManager'
import { appRecovery, common, Want } from '@kit.AbilityKit'
import { BusinessError } from '@kit.BasicServicesKit'

import { EZTimeUtil } from '@ohos/EZOpenSDK'
import EZLog from './EZLog'
import EZFileUtil from './EZFileUtil'

export default class EZCrashUtil {
  private constructor() {
  }

  private static readonly idKey: string = 'EzOpenSDKCrashLog'
  private static observerId: number = -100;
  //错误日志文件路径
  private static ErrorFilePath: string = ``
  private static readonly crashLogDirPath = "CrashLog"

    /**
     * 模拟crash
     *   let tempList = ['0', '1'];
     *   tempList[5].toString();
     */

  private static crashLogName(): string {
    return `${EZTimeUtil.formatDateForFile(new Date())}_crash.txt`
  }

  /**
   * 注册错误观测器。注册后可以捕获到应用产生的js crash，应用崩溃时进程不会退出。将异常信息写入本地文件。
   */
  static onError() {
    try {
      let cacheId = EZCrashGlobalContext.getContext().getObject(EZCrashUtil.idKey) as number;
      if (cacheId !== undefined && cacheId !== -100) {
        EZCrashUtil.offError(); //如果存在，就先注销错误观测器。
      }
      EZCrashUtil.observerId = errorManager.on('error', {
        onUnhandledException(errMsg) {
          let errStr = `${EZTimeUtil.formatDateToISO(new Date())} - 异常信息：\n${errMsg}\n\n\n`;
          EZLog.error(`EZCrashUtil-onError->:(${EZCrashUtil.crashLogName()})errMsg: ${errStr}`);
          EZCrashUtil.ErrorFilePath =
            EZFileUtil.getCacheDirPath(EZCrashUtil.crashLogDirPath, EZCrashUtil.crashLogName())
          EZFileUtil.writeEasy(EZCrashUtil.ErrorFilePath, errStr);
        },
        onException(errObject) {
          // let errStr = `onException - 异常信息2：\n${JSON.stringify(errObject)}\n\n\n`;
          //EZLog.error(`EZCrashUtil-onException-异常 ~ errStr: ${errStr}`);
        }
      });
      EZCrashGlobalContext.getContext().setObject(EZCrashUtil.idKey, EZCrashUtil.observerId);
    } catch (err) {
      let error = err as BusinessError;
      EZLog.error(`EZCrashUtil-catchOnError->: code: ${error.code}; message: ${error.message}`);
    }
  }

  /**
   * 注销错误观测器。
   */
  static offError() {
    try {
      if (EZCrashUtil.observerId === -100) {
        let cacheId = EZCrashGlobalContext.getContext().getObject(EZCrashUtil.idKey) as number;
        if (cacheId && cacheId != -100) {
          EZCrashUtil.observerId = cacheId;
        }
      }
      errorManager.off('error', EZCrashUtil.observerId, (err: BusinessError) => {
        if (err) {
          EZLog.error("EZCrashUtil：" + JSON.stringify(err))
          return;
        }
        EZCrashGlobalContext.getContext().setObject(EZCrashUtil.idKey, -100);
      });
    } catch (err) {
      let error = err as BusinessError;
      EZLog.error(`EZCrashUtil-catchOffError-> code: ${error.code} ; message: ${error.message}`);
    }
  }


  /**
   * 导出错误日志
   */
  static onExportErrorLog() {
    EZCrashUtil.ErrorFilePath = EZFileUtil.getCacheDirPath(EZCrashUtil.crashLogDirPath, EZCrashUtil.crashLogName())
    if (EZCrashUtil.ErrorFilePath) {
      EZFileUtil.saveDocument([`${EZCrashUtil.crashLogName()}`]).then((documentSaveResult: Array<string>) => {
        if (documentSaveResult && documentSaveResult.length > 0) {
          let saveUri = documentSaveResult[0];
          let file = EZFileUtil.openSync(saveUri);
          EZFileUtil.copyFile(EZCrashUtil.ErrorFilePath, file.fd).then(() => {
            EZFileUtil.close(file.fd);
            EZLog.warn(`EZCrashUtil: ExportErrorLog succeed(${file.path})`)
          });
        }
      })
    } else {
      EZLog.warn("EZCrashUtil:暂无日志文件")
    }
  }


  /**
   * 读取错误日志文件
   */
  static async readErrorText(): Promise<string> {
    EZCrashUtil.ErrorFilePath = EZFileUtil.getCacheDirPath(EZCrashUtil.crashLogDirPath, EZCrashUtil.crashLogName())
    if (EZCrashUtil.ErrorFilePath) {
      if (EZFileUtil.accessSync(EZCrashUtil.ErrorFilePath)) {
        return await EZFileUtil.readText(EZCrashUtil.ErrorFilePath);
      }else{
        EZLog.warn(`EZCrashUtil-readErrorText warn->:ErrorFilePath not found(${EZCrashUtil.ErrorFilePath.toString()})`);
      }
    } else {
      EZLog.warn(`EZCrashUtil-readErrorText warn->:ErrorFilePath is Empty`);
    }
    return '';
  }


  /**
   * 启用应用恢复功能，参数按顺序填入。该接口调用后，应用从启动器启动时第一个Ability支持恢复。
   * @param restart RestartFlag 应用重启标志。
   *    ALWAYS_RESTART  0  总是重启应用。
   *    RESTART_WHEN_JS_CRASH  0x0001  发生JS_CRASH时重启应用。
   *    RESTART_WHEN_APP_FREEZE  0x0002  发生APP_FREEZE时重启应用。
   *    NO_RESTART  0xFFFF  总是不重启应用。
   * @param saveOccasion SaveOccasionFlag 保存条件标志
   *    SAVE_WHEN_ERROR  0x0001  当发生应用故障时保存。
   *    SAVE_WHEN_BACKGROUND  0x0002  当应用切入后台时保存。
   * @param saveMode SaveModeFlag  状态保存标志
   *    SAVE_WITH_FILE  0x0001  每次状态保存都会写入到本地文件缓存。
   *    SAVE_WITH_SHARED_MEMORY  0x0002  状态先保存在内存中，应用故障退出时写入到本地文件缓存。
   */
  static enableAppRecovery(restart: appRecovery.RestartFlag = appRecovery.RestartFlag.ALWAYS_RESTART,
    saveOccasion: appRecovery.SaveOccasionFlag = appRecovery.SaveOccasionFlag.SAVE_WHEN_ERROR,
    saveMode: appRecovery.SaveModeFlag.SAVE_WITH_FILE = appRecovery.SaveModeFlag.SAVE_WITH_FILE) {
    appRecovery.enableAppRecovery(restart, saveOccasion, saveMode);
  }


  /**
   * 重启APP，并拉起应用启动时第一个Ability，可以配合errorManager相关接口使用。
   * 如果该Ability存在已经保存的状态，这些状态数据会在Ability的OnCreate生命周期回调的want参数中作为wantParam属性传入。
   * API10时将启动由setRestartWant指定的Ability。如果没有指定则按以下规则启动：
   *   如果当前应用前台的Ability支持恢复，则重新拉起该Ability。
   *   如果存在多个支持恢复的Ability处于前台，则只拉起最后一个。
   *   如果没有Ability处于前台，则不拉起。
   */
  static restartApp() {
    appRecovery.restartApp()
  }


  /**
   * 保存当前App状态 或 主动保存Ability的状态，这个状态将在下次恢复启动时使用。可以配合errorManager相关接口使用
   * @param context UIAbilityContext  需要保存状态的UIAbility所对应的context。
   * @returns
   */
  static saveAppState(context?: common.UIAbilityContext): boolean {
    if (context) {
      return appRecovery.saveAppState(context) //主动保存Ability的状态
    } else {
      return appRecovery.saveAppState() //保存当前App状态
    }
  }


  /**
   * 设置下次恢复主动拉起场景下的Ability。该Ability必须为当前包下的UIAbility。
   * @param want 通过设置Want中"bundleName"和"abilityName"字段来指定恢复重启的Ability。
   */
  static setRestartWant(want: Want) {
    appRecovery.setRestartWant(want);
  }
}

export class EZCrashGlobalContext {
  private constructor() {
  }


  private static instance: EZCrashGlobalContext;
  private _objects = new Map<string, Object>();

  public static getContext(): EZCrashGlobalContext {
    if (!EZCrashGlobalContext.instance) {
      EZCrashGlobalContext.instance = new EZCrashGlobalContext();
    }
    return EZCrashGlobalContext.instance;
  }

  getObject(value: string): Object | undefined {
    return this._objects.get(value);
  }

  setObject(key: string, objectClass: Object): void {
    this._objects.set(key, objectClass);
  }
}