import { DocumentDirectoryPath } from "react-native-fs";
import { MY_VCS_STORE_KEY } from "../constants";
import { decryptJson, encryptJson } from "../cryptoutil/cryptoUtil";
import fileStorage from "../fileStorage";
import Storage, { MMKV } from "../storage";
import { TelemetryConstants } from "../telemetry/TelemetryConstants";
import { getErrorEventData, sendErrorEvent } from "../telemetry/TelemetryUtils";
import { VCMetadata } from "../VCMetadata";
import { verifyCredentialData } from "../openId4VCI/Utils";

export async function loadBackupData(
    data: string,
    encryptionKey: string,
  ): Promise<any> {
    const PREV_BACKUP_STATE_PATH = `${DocumentDirectoryPath}/.prev`;
    try {
      await handlePreviousBackup(PREV_BACKUP_STATE_PATH, encryptionKey);
      const completeBackupData = JSON.parse(data);
      const backupStartState = Date.now().toString();
      await fileStorage.writeFile(PREV_BACKUP_STATE_PATH, backupStartState);
      const dataFromDB = await loadVCs(completeBackupData, encryptionKey);
      await updateWellKnownConfigs(dataFromDB, encryptionKey);
    } catch (error) {
      console.error('Error while loading backup data:', error);
      throw error;
    }
  }

/**
   * Handle cleanup of previous backup state
   * @param prevBackupStatePath Path to previous backup state file
   * @param encryptionKey Encryption key
   * @returns Previous restore timestamp
   */
async function handlePreviousBackup(
    prevBackupStatePath: string,
    encryptionKey: string,
  ) {
    const previousBackupExists = await fileStorage.exists(prevBackupStatePath);
    if (previousBackupExists) {
      const previousRestoreTimestamp = await fileStorage.readFile(
        prevBackupStatePath,
      );
      const timestampNum = parseInt(previousRestoreTimestamp.trim());
      await unloadVCs(encryptionKey, timestampNum);
    }
  }

  /**
   * Unload Verifiable Credentials based on a cutoff timestamp
   * @param encryptionKey Encryption key
   * @param cutOffTimestamp Timestamp to use as cutoff
   */
  async function unloadVCs(
    encryptionKey: string,
    cutOffTimestamp: number,
  ) {
    try {
      await removeOldVCFiles(cutOffTimestamp);
      await removeVCMetadataFromDB(encryptionKey, cutOffTimestamp);
    } catch (error) {
      console.error('Error while unloading VCs:', error);
      sendErrorEvent(
        getErrorEventData(
          TelemetryConstants.FlowType.dataRestore,
          TelemetryConstants.ErrorId.failure,
          error.message,
        ),
      );
      throw error;
    }
  }

  /**
   * Remove VC files created after the cutoff timestamp
   * @param cutOffTimestamp Timestamp to use as cutoff
   */
  async function removeOldVCFiles(cutOffTimestamp: number) {
    const vcDirectory = `${DocumentDirectoryPath}/inji/VC/`;
    const files = await fileStorage.getAllFilesInDirectory(vcDirectory);

    const filesToRemove = files.filter(file => {
      const fileName = file.name.split('.')[0];
      const fileTimestamp = parseInt(fileName.split('_')[1]);
      return fileTimestamp >= cutOffTimestamp;
    });

    await Promise.all(
      filesToRemove.map(file => fileStorage.removeItem(file.path)),
    );
  }

  /**
   * Update MMKV store to remove VCs after cutoff timestamp
   * @param encryptionKey Encryption key
   * @param cutOffTimestamp Timestamp to use as cutoff
   */
  async function removeVCMetadataFromDB(
    encryptionKey: any,
    cutOffTimestamp: number,
  ) {
    const myVCsEnc = await MMKV.getItem(MY_VCS_STORE_KEY, encryptionKey);

    if (myVCsEnc === null) return;

    const mmkvVCs = await decryptJson(encryptionKey, myVCsEnc);
    const vcList: VCMetadata[] = JSON.parse(mmkvVCs);
    const newVCList = vcList.filter(
      vc => !vc.timestamp || parseInt(vc.timestamp) < cutOffTimestamp,
    );
    const finalVC = await encryptJson(encryptionKey, JSON.stringify(newVCList));
    await MMKV.setItem(MY_VCS_STORE_KEY, finalVC);
  }

  /**
   * Handle cleanup of previous backup state
   * @param completeBackupData Entire Backup data to be loaded
   * @param encryptionKey Encryption key
   * @returns Previous
   */
  async function loadVCs(
    completeBackupData: Record<string, any>,
    encryptionKey: string,
  ): Promise<Record<string, any> | undefined> {
    try {
      const {VC_Records: allVCs, dataFromDB: dataFromDB} = completeBackupData;

      await processAndStoreVCs(allVCs, dataFromDB, encryptionKey);
      await updateMyVcList(dataFromDB, encryptionKey);
      await fileStorage.removeItemIfExist(`${DocumentDirectoryPath}/.prev`);
      return dataFromDB;
    } catch (error) {
      console.error('Error while loading VCs:', error);
      sendErrorEvent(
        getErrorEventData(
          TelemetryConstants.FlowType.dataBackup,
          TelemetryConstants.ErrorId.failure,
          error.message,
        ),
      );
      throw error;
    }
  }

  /**
   * Process and store individual Verifiable Credentials
   * @param allVCs All Verifiable Credentials from backup
   * @param dataFromDB Database data containing VC metadata
   * @param encryptionKey Encryption key
   */
  async function processAndStoreVCs(
    allVCs: Record<string, any>,
    dataFromDB: Record<string, any>,
    encryptionKey: string,
  ) {
    const vcKeys = Object.keys(allVCs);

    await Promise.all(
      vcKeys.map(async key => {
        const vcData = allVCs[key];
        // Add randomness to timestamp to maintain uniqueness
        const timestamp = Date.now() + Math.random().toString().substring(2, 10);
        const prevUnixTimeStamp = vcData.vcMetadata.timestamp;

        //verify the credential and update the metadata
        const verifiableCredential = vcData.verifiableCredential?.credential || vcData.verifiableCredential;
        const verificationResult = await verifyCredentialData(verifiableCredential, vcData.vcMetadata.format, vcData.vcMetadata.issuer)
        const isVerified = verificationResult.isVerified;
        
        vcData.vcMetadata.timestamp = timestamp;
        vcData.vcMetadata.isVerified = isVerified;

        //update the vcMetadata
        dataFromDB.myVCs.forEach(myVcMetadata => {
          if (
            myVcMetadata.requestId === vcData.vcMetadata.requestId &&
            myVcMetadata.timestamp === prevUnixTimeStamp
          ) {
            myVcMetadata.timestamp = timestamp;
            myVcMetadata.isVerified = isVerified;
          }
        });

        // Encrypt and store the VC
        const updatedVcKey = new VCMetadata(vcData.vcMetadata).getVcKey();
        const encryptedVC = await encryptJson(
          encryptionKey,
          JSON.stringify(vcData),
        );
        await Storage.setItem(updatedVcKey, encryptedVC, encryptionKey);
      }),
    );
  }

  /**
   * Update MMKV store with new VC metadata
   * @param dataFromDB Database data containing VC metadata
   * @param encryptionKey Encryption key
   */
  async function updateMyVcList(
    dataFromDB: Record<string, any>,
    encryptionKey: string,
  ) {
    const dataFromMyVCKey = dataFromDB[MY_VCS_STORE_KEY];
    const encryptedMyVCKeyFromMMKV = await MMKV.getItem(MY_VCS_STORE_KEY);

    let newDataForMyVCKey: VCMetadata[];

    if (encryptedMyVCKeyFromMMKV != null) {
      const myVCKeyFromMMKV = await decryptJson(
        encryptionKey,
        encryptedMyVCKeyFromMMKV,
      );

      newDataForMyVCKey = [...JSON.parse(myVCKeyFromMMKV), ...dataFromMyVCKey];
    } else {
      newDataForMyVCKey = dataFromMyVCKey;
    }
    const encryptedDataForMyVCKey = await encryptJson(
      encryptionKey,
      JSON.stringify(newDataForMyVCKey),
    );

    await Storage.setItem(
      MY_VCS_STORE_KEY,
      encryptedDataForMyVCKey,
      encryptionKey,
    );
  }

  /**
   * Update Well Known configs for cached issuers
   * @param dataFromDB Database data
   * @param encryptionKey Encryption key
   */
  async function updateWellKnownConfigs(
    dataFromDB: Record<string, any>,
    encryptionKey: string,
  ) {
    const cacheKeys = Object.keys(dataFromDB).filter(key =>
      key.includes('CACHE_FETCH_ISSUER_WELLKNOWN_CONFIG_'),
    );

    await Promise.all(
      cacheKeys.map(async key => {
        const value = dataFromDB[key];
        const encryptedValue = await encryptJson(
          encryptionKey,
          JSON.stringify(value),
        );
        await Storage.setItem(key, encryptedValue, encryptionKey);
      }),
    );
}