/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import common from '@ohos.app.ability.common';
import rdb from '@ohos.data.relationalStore';
import { ValuesBucket } from '@ohos.data.ValuesBucket';
import contextConstant from '@ohos.app.ability.contextConstant';
import relationalStore from '@ohos.data.relationalStore';
import LogUtils from '../utils/LogUtils';
import { ArrayUtil } from '../utils/ArrayUtil';
import { SystemModeController } from '../utils/SystemModeController';

const RETRY_COUNT = 3;
const DELAY_TIMES = 100;
const DELAY_TIME_ARRAY = [50, 100, 200, 400, 800];
const NEED_RETRY_CODE = new Set([14800015, 14800024, 14800025, 14800026, 14800028, 14800029]);

/**
 * 单独给云同步用的 rdb helper,因为克隆、双升单的help涉及建库场景,云同步不涉及
 */
export class BaseRdbHelperForCloudSync {
  private readonly logTag: string = '';
  protected rdbStoreHelper?: rdb.RdbStore;

  constructor(logTag: string) {
    this.logTag = `[${logTag}::BaseRdbHelperForCloudSync]`;
  }

  public async getRdbStoreHelper(context: common.Context, dbName: string,
      isSupportSearch: boolean): Promise<rdb.RdbStore | undefined> {
    LogUtils.w(this.logTag, `getRdbStoreHelper start,dbName:${dbName}`);
    if (context === undefined || context === null) {
      LogUtils.w(this.logTag, 'getRdbStoreHelper failed context is empty');
      return undefined;
    }
    context.area = contextConstant.AreaMode.EL2;
    let rdbStoreHelper: rdb.RdbStore | undefined = undefined;
    try {
      const CONFIG: rdb.StoreConfig = {
        name: dbName,
        //云同步连接单框架db,改为正确等级S3
        securityLevel: rdb.SecurityLevel.S3,
        isSearchable: isSupportSearch,
        autoCleanDirtyData: false,
        customDir: '../../rdb',
        allowRebuild: true
      };
      rdbStoreHelper = await rdb.getRdbStore(context, CONFIG);
      if (rdbStoreHelper != null) { // 如果开库成功,
        const schemaVersion = await rdbStoreHelper.execute('pragma schema_version;');
        LogUtils.w(this.logTag, `initDB end, contact db version is:${rdbStoreHelper.version},schemaVersion:${schemaVersion}`);
        let rebuilt: number = rdbStoreHelper.rebuilt;
        LogUtils.i(this.logTag, `getRdbStoreContacts, rebuilt:${rebuilt}`);
        if (rebuilt == rdb.RebuildType.REBUILT ) { // 如果进行了重建
          await rdbStoreHelper.restore('contacts.db.bak', (err) => {
            if (err) {
              LogUtils.e(this.logTag, `getRdbStoreContacts, Restore failed, code is ${err.code},message is ${err.message}`);
              return;
            }
            LogUtils.i(this.logTag, `getRdbStoreContacts, Restore success.`);
          }); // 则进行恢复
        } else {
          // 没有重建,什么也不做
          LogUtils.i(this.logTag, `getRdbStoreContacts, No rebuilt`);
        }
      } else {
        //开库失败 进行恢复
        LogUtils.e(this.logTag, `getRdbStoreContacts, GetRdbStore err, rdbStoreHelper is null, skip rebuilt. `);
        await (rdbStoreHelper as rdb.RdbStore).restore('contacts.db.bak', (err) => {
          if (err) {
            LogUtils.e(this.logTag, `Restore failed,code is ${err.code},message is ${err.message}`);
            return;
          }
          LogUtils.i(this.logTag, `Restore success.`);
        })
      }
      if (rdbStoreHelper != null) {
        // 开库成功之后,设置 distributedTables
        let distributedConfig: relationalStore.DistributedConfig = {
          autoSync: false,
          asyncDownloadAsset: true,
          enableCloud: SystemModeController.getInstance().getIsEnableContactCloudSync()
        };
        let tablesToSync: string[] = ['cloud_raw_contact', 'cloud_groups'];
        await rdbStoreHelper.setDistributedTables(tablesToSync, relationalStore.DistributedType.DISTRIBUTED_CLOUD,
          distributedConfig);
        LogUtils.w(this.logTag, 'getRdbStoreHelper,setDistributedTables success.');
      }
    } catch (error) {
      LogUtils.w(this.logTag, `getRdbStoreHelper failed,code is ${error.code},message is ${error.message}`);
    }
    return Promise.resolve(rdbStoreHelper);
  }

  protected async initDB(context: common.Context): Promise<rdb.RdbStore | undefined> {
    LogUtils.i(this.logTag, 'BaseRdbHelperForCloudSync initDB start');
    return Promise.resolve(undefined);
  }

  /**
   * query data
   *
   * @param predicates 查询条件
   * @param columns 查询列
   * @returns ResultSet
   */
  public async queryData(table: string, predicates: rdb.RdbPredicates, columns?: Array<string>)
    : Promise<rdb.ResultSet | undefined> {
    try {
      const ruleSet = await this.rdbStoreHelper!.query(predicates, columns);
      return ruleSet;
    } catch (e) {
      LogUtils.e(this.logTag, `queryData table:${table}, err code:${e.code}, msg:${e.message}`);
    }
    return undefined;
  }

  /**
   * Insert data into contact
   *
   * @param data which data to insert
   * @param tableName which table name to insert
   * @returns the number of successful insertions
   */
  public async insertData(data: ValuesBucket, tableName: string): Promise<number> {
    if (data == undefined) {
      LogUtils.e(this.logTag, 'insertData ValuesBucket is NULL');
      return Promise.resolve(-1);
    }
    if (this.rdbStoreHelper === undefined) {
      LogUtils.e(this.logTag, 'rdbStore is NULL');
      return Promise.resolve(-1);
    }
    return this.executeSqlFuncWithRetry(() => this.rdbStoreHelper!.insert(tableName, data)) as Promise<number>;
  }

  /**
   * 开启rdb事务
   *
   */
  public async beginTransaction() {
    let delayTime = [100, 250, 500, 1000, 2000, 4000];
    let retryIdx = 0;
    while (retryIdx <= delayTime.length - 1) {
      try {
        if (this.rdbStoreHelper === undefined) {
          LogUtils.e(this.logTag, `beginTransaction err,this.rdbStoreHelper is undefined`);
          return false;
        }
        await this.rdbStoreHelper.beginTransaction();
      } catch (e) {
        LogUtils.e(this.logTag, `beginTransaction err,retryIdx:${retryIdx},msg:${e.message}`);
        //未能获取事务,延迟对应时间尝试再次获取
        await this.delay(delayTime[retryIdx]);
        retryIdx ++;
        continue;
      }
      return true;
    }
    LogUtils.e(this.logTag, `after retried, beginTransaction err`);
    return false;
  }

  /**
   * 提交rdb事务
   *
   */
  public async commit() {
    try {
      if (this.rdbStoreHelper === undefined) {
        LogUtils.e(this.logTag, `commit err,this.rdbStoreHelper is undefined`);
        return -1;
      }
      await this.rdbStoreHelper.commit();
      return 0;
    } catch (e) {
      LogUtils.e(this.logTag, `commit err,msg:${e.message}`);
      return -1;
    }
  }

  /**
   * 回滚rdb事务
   *
   */
  public async rollBack() {
    try {
      if (this.rdbStoreHelper === undefined) {
        LogUtils.e(this.logTag, `rollBack err,this.rdbStoreHelper is undefined`);
        return -1;
      }
      await this.rdbStoreHelper.rollBack();
      return 0;
    } catch (e) {
      LogUtils.e(this.logTag, `rollBack err,msg:${e.message}`);
      return -1;
    }
  }

  /**
   * Update data into contact
   *
   * @param data which data to update
   * @param tableName which table name to update
   * @returns the number of successful insertions
   */
  public async updateData(data: ValuesBucket, predicates: rdb.RdbPredicates): Promise<number> {
    if (data == undefined || predicates == undefined) {
      LogUtils.e(this.logTag, 'updateData ValuesBucket or predicates is NULL');
      return -1;
    }
    if (this.rdbStoreHelper === undefined) {
      LogUtils.e(this.logTag, 'rdbStore is NULL');
      return -1;
    }
    return this.executeSqlFuncWithRetry(() => this.rdbStoreHelper!.update(data, predicates)) as Promise<number>;
  }

  /**
   * delete contact data
   *
   * @param data which data to contact
   * @param tableName which table name to contact
   * @returns the number of successful insertions
   */
  public async deleteData(predicates: rdb.RdbPredicates): Promise<number> {
    if (predicates == undefined) {
      LogUtils.e(this.logTag, 'deleteData predicates is NULL');
      return -1;
    }
    if (this.rdbStoreHelper === undefined) {
      LogUtils.e(this.logTag, 'rdbStore is NULL');
      return -1;
    }
    let ret = -1;
    try {
      ret = await this.rdbStoreHelper.delete(predicates);
    } catch (error) {
      LogUtils.e(this.logTag, `deleteData err,msg:${error.message}`);
    }
    return ret;
  }

  /**
   * Batch insert data into contact
   *
   * @param contactsDataList which data to insert
   * @param tableName which table name to insert
   * @returns the number of successful insertions
   */
  public async insertBatchData(contactsDataList: ValuesBucket[], tableName: string): Promise<number> {
    if (ArrayUtil.isEmpty(contactsDataList)) {
      LogUtils.e(this.logTag, 'contactsDataList is NULL');
      return -1;
    }
    if (this.rdbStoreHelper === undefined) {
      LogUtils.e(this.logTag, 'rdbStore is NULL');
      return -1;
    }
    let ret = -1;
    try {
      ret = await this.rdbStoreHelper.batchInsert(tableName, contactsDataList);
    } catch (error) {
      LogUtils.e(this.logTag, `insertBatchData err,msg:${error.message}`);
    }
    return ret;
  }

  /**
   * executesql
   *
   * @param sql SQL string
   * @param logSuccess exec result
   * @param tableName which table name to insert
   * @returns the number of successful insertions
   */
  public async querySql(sql: string, bindArgs?: Array<rdb.ValueType>) : Promise<relationalStore.ResultSet | undefined> {
    if (bindArgs === undefined) {
      bindArgs = []
    }
    if (this.rdbStoreHelper === undefined) {
      LogUtils.e('ContactsRelation', 'current rdbStoreHelper undefined');
      return undefined
    } else {
      let resultSet = await this.rdbStoreHelper.querySql(sql, bindArgs);
      if (resultSet === undefined ) {
        LogUtils.e('ContactsRelation', 'query result undefined');
        return undefined;
      }
      return resultSet;
    }
  }

  /**
   * executesql,可能抛出异常,使用放在try catch中;如果在事务中使用,执行抛出异常后,可以处理回滚
   *
   * @param sql SQL string
   * @param logSuccess exec result
   * @param tableName which table name to insert
   * @returns the number of successful insertions
   */
  public async executeSql(sql: string, logSuccess?: string, bindArgs?: Array<rdb.ValueType>) {
    if (bindArgs === undefined) {
      bindArgs = []
    }
    try {
      await this.rdbStoreHelper?.executeSql(sql, bindArgs);
      if (logSuccess != undefined && logSuccess != null) {
        LogUtils.i('ContactsRelation', logSuccess);
      }
    } catch (err) {
      LogUtils.e('ContactsRelation', `sql failed, err code: ${err?.code}, err message: ${err?.message}`);
      throw new Error('baseRdbHelperForCloudSync executeSql error');
    }
  }

  public async delay(time: number): Promise<number> {
    return new Promise(resolve => setTimeout(resolve, time));
  }

  public async executeSqlFuncWithRetry(sqlFunc: Function): Promise<void | number> {
    let retryIdx = 0;
    let ret: void | number = -1;
    while (retryIdx <= DELAY_TIME_ARRAY.length - 1) {
      try {
        ret = await sqlFunc();
      } catch (e) {
        LogUtils.e(this.logTag, `executeSql err,retryIdx:${retryIdx},code:${e?.code},msg:${e?.message}`);
        if (NEED_RETRY_CODE.has(e?.code)) {
          await this.delay(DELAY_TIME_ARRAY[retryIdx]);
          retryIdx++;
          continue;
        }
      }
      return ret;
    }
    LogUtils.e(this.logTag, `after retried still failed`);
    return ret;
  }

  public async retryGetSingleRdbForCloudSync(baseHelper: BaseRdbHelperForCloudSync, context: common.Context,
    dbName: string, isSupportSearch: boolean): Promise<rdb.RdbStore | undefined> {
    let rdbStoreHelper: rdb.RdbStore | undefined = undefined;
    for (let i = 1; i <= RETRY_COUNT; i++) {
      LogUtils.i(this.logTag, `retryGetSingleRdb start: ${i}`);
      rdbStoreHelper = await baseHelper.getRdbStoreHelper(context, dbName, isSupportSearch);
      if (rdbStoreHelper) {
        LogUtils.i(this.logTag, `retryGetSingleRdb ${i} success`);
        break;
      }
      await this.delay(DELAY_TIMES);
    }
    return rdbStoreHelper;
  }
}