/**
   * Updates the Key detailed table for this account and key, with the passed addres
   *
   * @param hierarchyAccountId
   * @param ecKey
   * @param cryptoAddress
   * @param blockchainNetworkType
   */
  public void updateKeyDetailedStatsWithNewAddress(
      int hierarchyAccountId,
      ECKey ecKey,
      CryptoAddress cryptoAddress,
      BlockchainNetworkType blockchainNetworkType)
      throws CantExecuteDatabaseOperationException, UnexpectedResultReturnedFromDatabaseException {
    /** If we are not allowed to save detailed information then we will exit */
    if (!VaultKeyMaintenanceParameters.STORE_DETAILED_KEY_INFORMATION) return;

    DatabaseTable databaseTable =
        database.getTable(
            AssetsOverBitcoinCryptoVaultDatabaseConstants.KEY_MAINTENANCE_DETAIL_TABLE_NAME);
    databaseTable.addStringFilter(
        AssetsOverBitcoinCryptoVaultDatabaseConstants.KEY_MAINTENANCE_DETAIL_ACCOUNT_ID_COLUMN_NAME,
        String.valueOf(hierarchyAccountId),
        DatabaseFilterType.EQUAL);
    databaseTable.addStringFilter(
        AssetsOverBitcoinCryptoVaultDatabaseConstants.KEY_MAINTENANCE_DETAIL_PUBLIC_KEY_COLUMN_NAME,
        ecKey.getPublicKeyAsHex(),
        DatabaseFilterType.EQUAL);

    try {
      databaseTable.loadToMemory();
    } catch (CantLoadTableToMemoryException e) {
      throwLoadToMemoryException(e, databaseTable.getTableName());
    }

    if (databaseTable.getRecords().size() == 0) {
      StringBuilder output = new StringBuilder("The key " + ecKey.toString());
      output.append(System.lineSeparator());
      output.append("which generated the address " + cryptoAddress.getAddress());
      output.append(System.lineSeparator());
      output.append("is not a key derived from the vault.");

      throw new UnexpectedResultReturnedFromDatabaseException(
          null, output.toString(), "Vault derivation miss match");
    }

    DatabaseTableRecord record = databaseTable.getRecords().get(0);
    record.setStringValue(
        AssetsOverBitcoinCryptoVaultDatabaseConstants.KEY_MAINTENANCE_DETAIL_ADDRESS_COLUMN_NAME,
        cryptoAddress.getAddress());
    record.setStringValue(
        AssetsOverBitcoinCryptoVaultDatabaseConstants
            .KEY_MAINTENANCE_DETAIL_BLOCKCHAIN_NETWORK_TYPE_COLUMN_NAME,
        blockchainNetworkType.getCode());
    try {
      databaseTable.updateRecord(record);
    } catch (CantUpdateRecordException e) {
      throw new CantExecuteDatabaseOperationException(
          CantExecuteDatabaseOperationException.DEFAULT_MESSAGE,
          e,
          "error updating record",
          "database issue");
    }
  }
  /**
   * Insert new public keys into the detailed monitor table
   *
   * @param accountId
   * @param keys
   * @throws CantExecuteDatabaseOperationException
   */
  public void updateDetailMaintainerStats(int accountId, List<ECKey> keys, int currentGeneratedKeys)
      throws CantExecuteDatabaseOperationException {
    /** If we are not allowed to save detailed information then we will exit. */
    if (!VaultKeyMaintenanceParameters.STORE_DETAILED_KEY_INFORMATION) return;

    DatabaseTable databaseTable =
        database.getTable(
            AssetsOverBitcoinCryptoVaultDatabaseConstants.KEY_MAINTENANCE_DETAIL_TABLE_NAME);
    DatabaseTransaction transaction = database.newTransaction();

    /**
     * I will insert each key. Since I don't want to repeat inserting keys, I will only insert the
     * keys which position is after currentGeneratedKeys value
     */
    int i = 1;
    for (ECKey key : keys) {
      if (i >= currentGeneratedKeys) {
        DatabaseTableRecord record = databaseTable.getEmptyRecord();
        record.setIntegerValue(
            AssetsOverBitcoinCryptoVaultDatabaseConstants
                .KEY_MAINTENANCE_DETAIL_ACCOUNT_ID_COLUMN_NAME,
            accountId);
        record.setIntegerValue(
            AssetsOverBitcoinCryptoVaultDatabaseConstants
                .KEY_MAINTENANCE_DETAIL_KEY_DEPTH_COLUMN_NAME,
            i);
        record.setStringValue(
            AssetsOverBitcoinCryptoVaultDatabaseConstants
                .KEY_MAINTENANCE_DETAIL_PUBLIC_KEY_COLUMN_NAME,
            key.getPublicKeyAsHex());
        transaction.addRecordToInsert(databaseTable, record);
      }
      i++;
    }

    /** once I collected all records, I will insert them in a single transaction */
    try {
      database.executeTransaction(transaction);
    } catch (DatabaseTransactionFailedException e) {
      throw new CantExecuteDatabaseOperationException(
          CantExecuteDatabaseOperationException.DEFAULT_MESSAGE,
          e,
          "error inserting records in transaction.",
          null);
    }
  }
  private void doMigrateFrom_v1_0_262(Context context, NetworkParameters migrationParams)
      throws IOException {
    Log.d(TAG, "********* MIGRATION FROM v1.0.262 - " + migrationParams.getId() + "*********");

    final File rootDir = context.getFilesDir();
    final File storageDir;
    final File archiveDir = new File(rootDir, "archive_" + System.currentTimeMillis());
    final File walletFile;
    final File chainFile;

    final File newWalletFile;
    final File newChainFile;

    if (ClientUtils.isMainNet(migrationParams)) {
      String walletFilesPrefix = AppConfig.MainNetConfig.get().getWalletFilesPrefix();
      storageDir = new File(rootDir, "mainnet_wallet__uuid_object_storage");
      walletFile = new File(rootDir, "mainnet_wallet_.wallet");
      newWalletFile = new File(rootDir, walletFilesPrefix + ".wallet");
      chainFile = new File(rootDir, "mainnet_wallet_.spvchain");
      newChainFile = new File(rootDir, walletFilesPrefix + ".spvchain");
    } else if (ClientUtils.isTestNet(migrationParams)) {
      String walletFilesPrefix = AppConfig.TestNetConfig.get().getWalletFilesPrefix();
      storageDir = new File(rootDir, "testnet_wallet__uuid_object_storage");
      walletFile = new File(rootDir, "testnet_wallet_.wallet");
      newWalletFile = new File(rootDir, walletFilesPrefix + ".wallet");
      chainFile = new File(rootDir, "testnet_wallet_.spvchain");
      newChainFile = new File(rootDir, walletFilesPrefix + ".spvchain");
    } else {
      throw new RuntimeException(
          "Network params not supported (unknown): " + migrationParams.toString());
    }

    final File keyFile = new File(storageDir, "ECKeyWrapper.json");

    if (keyFile.exists() && walletFile.exists()) {

      // Keys: stored in ECKeyWrapper.json
      /* Key format (JSON):
        {
          "...uuid1...": {
              "isPublicOnly": true,
              "keyPayload": [...bytes (integers)...],
              "name": "remote_server_public_key",
              "uuid": "...uuid1..."
          },
          "...uuid2...": {
              "isPublicOnly": false,
              "keyPayload": [...bytes (integers)...],
              "name": "remote_client_public_key",
              "uuid": "...uuid2..."
          }
        }
      */

      Log.d(TAG, "Key file found: " + keyFile);
      String keyFileJson = FileUtils.readFileToString(keyFile);
      Type type = new TypeToken<Map<String, ECKeyWrapper>>() {}.getType();
      // Note: do not use gson from serializeutils (key is not stored in base64).
      Map<String, ECKeyWrapper> keys = new Gson().fromJson(keyFileJson, type);
      ECKey serverKey = null;
      ECKey clientKey = null;
      for (ECKeyWrapper key : keys.values()) {
        if (key.isPublicOnly && key.name.equals("remote_server_public_key")) {
          serverKey = ECKey.fromPublicOnly(key.keyPayload);
        } else if (!key.isPublicOnly && key.name.equals("remote_client_public_key")) {
          clientKey = ECKey.fromPrivate(key.keyPayload);
        } else {
          Log.d(TAG, "Unknown key name: " + key.name);
        }
      }

      if (clientKey != null && serverKey != null) {
        Log.d(TAG, "Found client and server keys - store in shared preferences.");
        try {

          /** ******** Actual Migration Code ********* */
          SharedPrefUtils.setClientKey(context, migrationParams, clientKey);
          SharedPrefUtils.setServerKey(context, migrationParams, serverKey, "n/a - migration");
          Log.d(
              TAG,
              "Migrated keys:"
                  + " clientPubKey="
                  + clientKey.getPublicKeyAsHex()
                  + ", serverPubKey="
                  + serverKey.getPublicKeyAsHex());

          // move wallet file
          Log.d(
              TAG,
              "Migrate wallet file: " + walletFile.toString() + " -> " + newWalletFile.toString());
          FileUtils.copyFile(walletFile, newWalletFile);
          Log.d(
              TAG,
              "Migrate chain file: " + chainFile.toString() + " -> " + newChainFile.toString());
          FileUtils.copyFile(chainFile, newChainFile);

          SharedPrefUtils.enableMultisig2of2ToCltvForwarder(context);

          // move everything to an archive file.
          Log.d(TAG, "Move old files to archive dir: " + archiveDir.toString());
          FileUtils.moveToDirectory(storageDir, archiveDir, true);
          FileUtils.moveToDirectory(walletFile, archiveDir, true);
          FileUtils.moveToDirectory(chainFile, archiveDir, true);

        } catch (Exception e) {
          Log.d(TAG, "Exception: ", e);
          // clear the changes made.
          SharedPrefUtils.setClientKey(context, migrationParams, null);
          SharedPrefUtils.setServerKey(context, migrationParams, null, "");
          if (newWalletFile.exists()) {
            newWalletFile.delete();
          }
        }
      }
    } else {
      Log.d(
          TAG,
          "Key file or wallet file not found - no migration required - "
              + String.format(
                  "keyFile: %s (exists: %s), walletFile: %s (exists: %s)",
                  keyFile.toString(),
                  keyFile.exists(),
                  walletFile.toString(),
                  walletFile.exists()));
    }

    Log.d(TAG, "********* MIGRATION FROM v1.0.262 FINISHED *********");
  }