import {
  AuthDetails,
  BNString,
  DeviceShareDescription,
  IModule,
  ITKeyApi,
  prettyPrintError,
  ShareStore,
  StringifiedType,
} from "@tkey-singularity/common-types";
import { generatePrivate, getPublic } from "@toruslabs/eccrypto";
import BN from "bn.js";

import WebStorageError from "./errors";
import { canAccessFileStorage, storeShareOnFileStorage } from "./FileStorageHelpers";
import { post } from "./http";
import { getPrivateKeyFromLocalStorage, storePrivateKeyOnLocalStorage } from "./LocalStorageHelpers";
import { decryptData, encryptData } from "./utils";
export const WEB_STORAGE_MODULE_NAME = "webStorage";

class WebStorageModule implements IModule {
  moduleName: string;

  tbSDK: ITKeyApi;

  canUseFileStorage: boolean;

  constructor(canUseFileStorage = true) {
    this.moduleName = WEB_STORAGE_MODULE_NAME;
    this.canUseFileStorage = canUseFileStorage;
    this.setFileStorageAccess();
  }

  async setFileStorageAccess(): Promise<void> {
    try {
      const result = await canAccessFileStorage();
      if (result.state === "denied") {
        this.canUseFileStorage = false;
      } else if (result.state === "granted") {
        this.canUseFileStorage = true;
      }
      // eslint-disable-next-line @typescript-eslint/no-this-alias
      const self = this;
      result.onchange = function permissionChange() {
        if (this.state === "denied") {
          self.canUseFileStorage = false;
        } else if (this.state === "granted") {
          self.canUseFileStorage = true;
        }
      };
    } catch (error) {}
  }

  setModuleReferences(tbSDK: ITKeyApi): void {
    this.tbSDK = tbSDK;
    this.tbSDK._setDeviceStorage(this.storeDeviceShare.bind(this));
  }

  // eslint-disable-next-line
  async initialize(): Promise<void> {}

  async storeDeviceShare(url: string, authDetails: AuthDetails, deviceShareStore: ShareStore, customDeviceInfo?: StringifiedType): Promise<unknown> {
    const encryptedShare = await this.encryptShareWithPublicKey(deviceShareStore);
    await storePrivateKeyOnLocalStorage(encryptedShare.privateKey.toString("hex"), authDetails.verifier);
    // await this.storeCookieShare(encryptedShare, authDetails.verifier, authDetails.apiKey, authDetails.bearerToken, url);
    const shareDescription: DeviceShareDescription = {
      module: this.moduleName,
      userAgent: navigator.userAgent,
      dateAdded: Date.now(),
    };
    if (customDeviceInfo) {
      shareDescription.customDeviceInfo = JSON.stringify(customDeviceInfo);
    }
    await this.tbSDK.addShareDescription(deviceShareStore.share.shareIndex.toString("hex"), JSON.stringify(shareDescription), true);
    return encryptedShare;
  }

  async storeDeviceShareOnFileStorage(shareIndex: BNString): Promise<void> {
    const metadata = this.tbSDK.getMetadata();
    const tkeypubx = metadata.pubKey.x.toString("hex");
    const shareStore = this.tbSDK.outputShareStore(new BN(shareIndex));
    return storeShareOnFileStorage(shareStore, tkeypubx);
  }

  async getDeviceShare(encryptedMessage: string, verifier: string): Promise<ShareStore> {
    let shareStore: ShareStore;
    try {
      shareStore = await this.getCookieShare(encryptedMessage, verifier);
    } catch (localErr) {
      throw WebStorageError.unableToReadFromStorage(`Error inputShareFromWebStorage: ${prettyPrintError(localErr)}`);
    }
    return shareStore;
  }

  async inputShareFromWebStorage(authDetails: AuthDetails, encryptedMessage: string): Promise<unknown> {
    const shareStore = await this.getCookieShare(encryptedMessage, authDetails.verifier);
    this.tbSDK.inputShareStore(shareStore);
    return {
      latestShareStore: shareStore,
    };
  }

  async encryptShareWithPublicKey(share: ShareStore): Promise<{ privateKey: Buffer; publickey: Buffer; encryptedShare: string }> {
    const privateKey = generatePrivate();
    const privKeyHex = privateKey.toString("hex");

    const publicKey = getPublic(privateKey);
    const encryptedShareMessage = await encryptData(privKeyHex, share);
    return {
      publickey: publicKey,
      privateKey,
      encryptedShare: encryptedShareMessage,
    };
  }

  async getCookieShare(encryptedString: string, verifier): Promise<ShareStore> {
    const privateKeyHex = await getPrivateKeyFromLocalStorage(verifier);
    const shareStore = await decryptData<ShareStore>(privateKeyHex, encryptedString);
    return shareStore;
  }

  async storeCookieShare(encryptedShare, verifier, apikey, bearerToken, url): Promise<unknown> {
    const body = JSON.stringify({ encryptedShare });
    const header = {
      credentials: "include",
      withCredentials: true,
      crossDomain: true,
      "x-api-key": apikey,
      authorization: `Bearer ${bearerToken}`,
      "Content-Type": "application/json",
    };
    return post(url, body, header);
  }

  async getMetaData(url, apikey, bearerToken): Promise<unknown> {
    const body = "";
    const header = {
      credentials: "include",
      withCredentials: true,
      crossDomain: true,
      "x-api-key": apikey,
      authorization: `Bearer ${bearerToken}`,
      "Content-Type": "application/json",
    };
    return post(url, body, header);
  }

  async getMetaDataFlow(url, apikey, bearerToken): Promise<unknown> {
    const metadata = this.getMetaData(url, apikey, bearerToken);
    if (metadata == null) {
      const sdkMetadata = this.tbSDK.getMetadata();
      await this.storeMetadata(url, apikey, bearerToken, sdkMetadata);
    } else {
      return metadata;
    }
  }

  async storeMetadata(url, apikey, bearerToken, sdkMetadata): Promise<unknown> {
    const body = JSON.stringify(sdkMetadata);
    const header = {
      credentials: "include",
      withCredentials: true,
      crossDomain: true,
      "x-api-key": apikey,
      authorization: `Bearer ${bearerToken}`,
      "Content-Type": "application/json",
    };
    return post(url, body, header);
  }
}

export default WebStorageModule;
