import { v4 as uuid } from 'uuid'
import crypto from 'crypto'
import { debugLog, Doc, env, Item, YMap } from "../../internals"
import { BasicSignature } from './signature'
// @ts-ignore
import VerifyWorker from 'web-worker:./verify.worker.js'
import { getSecretKeys, changeSignatureVerify, getSignatureLevelConfig } from './utils'

import CryptoJSW from '@originjs/crypto-js-wasm'
import NodeRSA from 'node-rsa'
// @ts-ignore
import ecdsa from 'ecdsa-wasm'
// @ts-ignore
import eccrypto from '@comtstore/eccrypto'

/**
 * 创建签名
 * @param {Item} item 
 * @param {Doc} doc 
 * @param {string} signal 
 * @returns {Array<string>}
 */
export const signSignature = (item, doc, signal) => {
  const { currentUser } = doc.signature
  const webAssembly = getSignatureLevelConfig(doc, 'signConfig', 'webAssembly')
  const type = getSignatureLevelConfig(doc, 'signConfig', 'type')
  const bit = getSignatureLevelConfig(doc, 'signConfig', 'bit')
  const light = getSignatureLevelConfig(doc, 'signConfig', 'light')
  /**
     * @type {YMap<any> | null}
     */
  const secretKeys = getSecretKeys(doc, item, currentUser, type, bit)
  if (!secretKeys) { 
      debugLog('signSignature failed: no secretKeys found.', item, doc, signal)
      return ['']
  }

  if(light){
    return [ '', secretKeys.get('id') ]
  }

  const privateKey = secretKeys.get('private')
  if (!privateKey) return ['']
  /**
   * @type {string}
   */
  let signatureResult = ''
  if (type === 0) {
    const loadingState = RSAWasmLoader.getInstance().getLoadingState()
    if (webAssembly && [0, 3].includes(loadingState)) {
      RSAWasmLoader.getInstance().load()
    }
    if (webAssembly && loadingState === 2) {
      signatureResult = RSAWasmLoader.getInstance().sign(signal, privateKey)
      debugLog('webAssembly进行rsa签名:', () => item.content.getContent(), signatureResult)
    } else {
      const bit = secretKeys.get('bit')
      signatureResult = RSAVerifier.getInstance().sign(signal, privateKey, bit)
      debugLog('普通rsa签名:', () => item.content.getContent(), signatureResult)
    }
  } else if (type === 1) {
    const loadingState = EcdsaWasmLoader.getInstance().getLoadingState()
    try {
      if (webAssembly && [0, 3].includes(loadingState)) {
        EcdsaWasmLoader.getInstance().load()
      }
      if (webAssembly && loadingState === 2) {
        signatureResult = EcdsaWasmLoader.getInstance().sign(signal, privateKey)
        debugLog('webAssembly签名:', () => item.content.getContent(), signatureResult)
      } else {
        signatureResult = EcdsaVerifier.getInstance().sign(signal, privateKey)
        debugLog('普通ecdsa签名:', () => item.content.getContent(), signatureResult)
      }
    } catch (err) {
      console.error(err)
    }
  }
  return [ signatureResult, secretKeys.get('id')]
}

/**
 * 
 * @param {{
 *   publicKey: string,
 *   type: number,
 *   bit: number,
 *   webAssembly: boolean,
 *   signal: string,
 *   content: string,
 *   rsaWasmLoaderInstance: RSAWasmLoader,
 *   rsaVerifierInstance: RSAVerifier,
 *   ecdsaWasmLoaderInstance: EcdsaWasmLoader,
 *   ecdsaVerifierInstance: EcdsaVerifier
 * }} options 
 * @returns {boolean}
 */
export const verifySignatureByParam = (options) => {
   const { publicKey, type, bit, webAssembly, signal, content, 
    rsaWasmLoaderInstance, rsaVerifierInstance,
    ecdsaWasmLoaderInstance, ecdsaVerifierInstance } = options
   /**
     * @type {boolean}
     */
   let verifyResult = false
  
   if (type === 0) {
     const loadingState = rsaWasmLoaderInstance.getLoadingState()
     if (webAssembly && [0, 3].includes(loadingState)) {
      rsaWasmLoaderInstance.load()
     }
     if (webAssembly && loadingState === 2) {
       verifyResult = rsaWasmLoaderInstance.verify(signal, publicKey, content)
       debugLog('webAssembly进行rsa校验:', () => signal, verifyResult)
     } else {
       verifyResult = rsaVerifierInstance.verify(signal, publicKey, content, bit)
       debugLog('普通进行rsa校验:', () => signal, verifyResult)
     }
   } else if (type === 1) {
     try {
       const loadingState = ecdsaWasmLoaderInstance.getLoadingState()
       if (webAssembly && [0, 3].includes(loadingState)) {
         ecdsaWasmLoaderInstance.load()
       }
       if (webAssembly && loadingState === 2) {
         verifyResult = ecdsaWasmLoaderInstance.verify(signal, publicKey, content)
         debugLog('webAssembly进行ecdsa校验:', () => signal, verifyResult)
       } else {
         verifyResult = ecdsaVerifierInstance.verify(signal, publicKey, content)
         debugLog('普通进行ecdsa校验:', () => signal, verifyResult)
       }
     } catch (err) {
       console.error(err)
     }
   }

   return verifyResult
}

/**
 * 验证一个签名和签名内容
 * @param {Item} item 
 * @param {Doc} doc 
 * @param {BasicSignature | null} signature 
 * @param {string} signal 
 * @returns {boolean | Promise<boolean>}
 */
export const verifySignature = (item, doc, signature, signal) => {
    if(!signature) return false
    if (signature.verify) {
      return signature.verify === 1
    }
  
    const webWorker = getSignatureLevelConfig(doc, 'verifyConfig', 'webWorker')
    const webAssembly = getSignatureLevelConfig(doc, 'verifyConfig', 'webAssembly')
    const light = getSignatureLevelConfig(doc, 'verifyConfig', 'light')

    // 开启轻签名，不进行验证 
    if(light) return true
  
    const type = signature.type
    /**
     * @type {YMap<any> | null}
     */
    const secretKeys = getSecretKeys(doc, item, signature.signatory, signature.type, undefined, signature.secretKeysId)
    if (!secretKeys) { 
        debugLog('verifySignature failed: no secretKeys found.', item, doc, signature, signal)
        return false
    }
  
    const publicKey = secretKeys.get('public')
    if (!publicKey) {
        debugLog('verifySignature failed: no publicKey found.', item, doc, signature, signal, secretKeys.toJSON())
        return false
    }
  
    if (webWorker) {
      return new Promise((resolve) => {
        if (!SignatureVerifyWorkerLoader.getInstance().worker) {
          SignatureVerifyWorkerLoader.getInstance().load()
        }
        SignatureVerifyWorkerLoader.getInstance().verify({
          type,
          bit: secretKeys.get('bit'),
          publicKey,
          webAssembly,
          signal,
          signature,
        }).then(resolve)
      })
    }
  
    /**
     * @type {boolean}
     */
    let verifyResult = verifySignatureByParam({
      publicKey,
      type,
      bit: secretKeys.get('bit'),
      webAssembly,
      signal,
      content: signature.content,
      rsaWasmLoaderInstance: RSAWasmLoader.getInstance(),
      rsaVerifierInstance: RSAVerifier.getInstance(),
      ecdsaWasmLoaderInstance: EcdsaWasmLoader.getInstance(),
      ecdsaVerifierInstance: EcdsaVerifier.getInstance()
    })

    changeSignatureVerify(signature, verifyResult)
    if (!verifyResult) {
      debugLog('verifySignature result = false: ', item)
    }
    return verifyResult
}  

export class EcdsaVerifier {
    /**
    * @type {EcdsaVerifier}
    */
    static instance
 
    /**
     * 获取实例
     * @returns {EcdsaVerifier} 
     */
    static getInstance(){
      if(!EcdsaVerifier.instance){
       EcdsaVerifier.instance = new EcdsaVerifier()
      }
      return EcdsaVerifier.instance
    }
 
    /**
     * 
     * @param {string} signal 
     * @param {string} privateKey 
     * @returns 
     */
    sign(signal, privateKey){
      return JSON.stringify(eccrypto.signSync(
        Buffer.from(JSON.parse(privateKey).data),
        crypto.createHash('sha256').update(signal).digest()
      ))
    }

    /**
     * 
     * @param {string} signal 
     * @param {string} publicKey 
     * @param {BasicSignature | string} signature 
     * @returns 
     */
    verify(signal, publicKey, signature){
      let content = typeof signature === 'string' ? signature : signature.content
      return eccrypto.verifySync(
        Buffer.from(JSON.parse(publicKey).data),
        crypto.createHash('sha256').update(signal).digest(),
        Buffer.from(JSON.parse(content).data)
      )  
    }
 }
 
export class RSAVerifier {
   /**
      * @type {RSAVerifier}
      */
   static instance
 
   /**
     * 获取实例
     * @returns {RSAVerifier} 
     */
   static getInstance(){
     if(!RSAVerifier.instance){
       RSAVerifier.instance = new RSAVerifier()
     }
     return RSAVerifier.instance
   }

   /**
    * 
    * @param {string} signal 
    * @param {string} privateKey 
    * @param {number} bit 
    * @returns 
    */
   sign(signal, privateKey, bit){
    // @ts-ignore
    const key = new NodeRSA({ b: bit }, {
      environment: env.getEnv() === 'node' ? 'node' : 'browser'
    })
    key.importKey(privateKey, 'pkcs8-private-pem')
    return JSON.stringify(key.sign(Buffer.from(signal)))
   }
 
   /**
     * 
     * @param {string} signal 
     * @param {string} publicKey
     * @param {BasicSignature | string} signature 
     * @param {number} bit
     * @returns 
     */
   verify(signal, publicKey, signature, bit){
     let content = typeof signature === 'string' ? signature : signature.content
     // @ts-ignore
     const key = new NodeRSA({ b: bit }, {
       environment: env.getEnv() === 'node' ? 'node' : 'browser'
     })
     key.importKey(publicKey, 'pkcs8-public-pem')
     return key.verify(Buffer.from(signal), Buffer.from(JSON.parse(content).data)) 
   }
 }
 
export class EcdsaWasmLoader {
   /**
    * @type {EcdsaWasmLoader}
    */
   static instance
 
   /**
    * 获取实例
    * @returns {EcdsaWasmLoader} 
    */
   static getInstance(){
     if(!EcdsaWasmLoader.instance){
       EcdsaWasmLoader.instance = new EcdsaWasmLoader()
     }
     return EcdsaWasmLoader.instance
   }
 
   constructor(){
     this.loadingState = 0 // 0 = 未开始加载， 1 = 正在加载， 2 = 加载完成， 3 = 加载失败
   }
 
   getLoadingState(){
     return this.loadingState
   }
 
   load(){
     this.loadingState = 1
     ecdsa.init().then(() => {
       // ecdsa.setSerializeMode(ecdsa.SerializeOld)
       this.loadingState = 2
     }).catch(() => {
       this.loadingState = 3
     })
   }

   /**
    * 
    * @param {string} signal 
    * @param {string} privateKey 
    * @returns 
    */
   sign(signal, privateKey){
    const _privateKey = Buffer.from(JSON.parse(privateKey).data).toString('hex')
    try {
      const _sec = ecdsa.deserializeHexStrToSecretKey(_privateKey)
      const sig = JSON.stringify(Buffer.from(_sec.sign(signal).serializeToHexStr(), 'hex'))
      return sig
    } catch (err) {
      debugLog('webAssembly sign出现错误:', err)
      return ''
    }
   }
 
   /**
    * 
    * @param {string} signal 
    * @param {string} publicKey 
    * @param {BasicSignature | string} signature 
    * @returns {boolean}
    */
   verify (signal, publicKey, signature) {
    let content = typeof signature === 'string' ? signature : signature.content
    const _publicKey = Buffer.from(JSON.parse(publicKey).data).toString('hex')
    const _signature = Buffer.from(JSON.parse(content).data).toString('hex')
    try {
      const _pub = ecdsa.deserializeHexStrToPublicKey(_publicKey)
      const _sig = ecdsa.deserializeHexStrToSignature(_signature)
      return _pub.verify(_sig, signal)
    } catch (err) {
      debugLog('webAssembly verify出现错误:', err)
      return false
    }
   }
 }
 
export class RSAWasmLoader {
   /**
    * @type {RSAWasmLoader}
    */
   static instance
 
   /**
    * 获取实例
    * @returns {RSAWasmLoader} 
    */
   static getInstance(){
     if(!RSAWasmLoader.instance){
       RSAWasmLoader.instance = new RSAWasmLoader()
     }
     return RSAWasmLoader.instance
   }
 
   constructor(){
     this.loadingState = 0
   }
 
   getLoadingState(){
     return this.loadingState
   }
 
   load(){
     this.loadingState = 1
     CryptoJSW.RSA.loadWasm().then(() => {
       this.loadingState = 2
     }).catch(() => {
       this.loadingState = 3
     })
   }

   /**
    * 
    * @param {string} signal 
    * @param {string} privateKey 
    * @returns {string}
    */
   sign(signal, privateKey){
    const RSA = CryptoJSW.RSA
    return JSON.stringify(Buffer.from(
      RSA.sign(
        new Uint8Array(crypto.createHash('sha256').update(signal).digest()), {
          isPublicKey: false,
          key: privateKey,
          signPadding: 'PKCS1V15'
        }
      ).buffer))
   }
 
   /**
    * 
    * @param {string} signal 
    * @param {string} publicKey 
    * @param {BasicSignature | string} signature 
    * @returns 
    */
   verify(signal, publicKey, signature){
    let content = typeof signature === 'string' ? signature : signature.content
    const RSA = CryptoJSW.RSA
    const verifyResult = RSA.verify(
      new Uint8Array(crypto.createHash('sha256').update(signal).digest()),
      new Uint8Array(Buffer.from(JSON.parse(content).data)),
      {
        isPublicKey: true,
        key: publicKey,
        signPadding: 'PKCS1V15'
      }
    )
    return verifyResult
   }
 }
 
 class SignatureVerifyWorkerLoader {
   /**
    * @type {SignatureVerifyWorkerLoader}
    */
   static instance
 
   /**
    * 获取实例
    * @returns {SignatureVerifyWorkerLoader} 
    */
   static getInstance(){
     if(!SignatureVerifyWorkerLoader.instance){
       SignatureVerifyWorkerLoader.instance = new SignatureVerifyWorkerLoader()
     }
     return SignatureVerifyWorkerLoader.instance
   }
   
   constructor(){
     /**
      * @type {any}
      */
     this.worker = null
   }
 
   load(){
    if(!this.worker){
      this.worker = new VerifyWorker()
      this.worker.onerror = (/** @type {any} */ e) => {
        console.log("SignatureVerifyWorkerLoader ~ worker get error:", e)
      }
    }
   }
 
   /**
    * @param {{
    *   publicKey: string,
    *   webAssembly: boolean,
    *   signature: BasicSignature
    *   type: number,
    *   signal: string,
    *   bit: number }} options
    */
   verify(options){
     const vid = uuid()
     return new Promise((resolve) => {
       this.worker.addEventListener('message', (/** @type {any} */ e) => {
         const data = JSON.parse(e.data)
         const { id, result } = data
         if (id === vid) {
           const verifyResult = result === 1
           changeSignatureVerify(options.signature, verifyResult)
           debugLog('通过webWorker获取到验证结果', id, result)
           resolve(verifyResult)
         }
       })
       this.worker.postMessage(JSON.stringify({
         id: vid,
         publicKey: options.publicKey,
         webAssembly: options.webAssembly,
         type: options.type,
         signal: options.signal,
         content: options.signature.content,
         bit: options.bit
       }))
     })
   }
 }
 