import { decoding, encoding } from "lib0"
import { uuidv4 } from "lib0/random"
import { debugLog, Doc, findIndexSS, ID, Item, YMap } from "../../internals"
import { getItemSignal, getSecretKeys, getSignatureLevelConfig } from './utils'
import { signSignature, verifySignature } from './verifier'

/**
 * 
 * @param {Item} item 
 * @param {Doc} doc 
 * @param {Array<string>} signalArray 
 * @returns 
 */
const signItemSignal = (item, doc, signalArray) => {
  const { currentUser } = doc.signature
  const type = getSignatureLevelConfig(doc, 'signConfig', 'type')
  /**
   * @type {Array<Signature>}
   */
  const signatureArray = []
  for (let i = 0; i < signalArray.length; i++) {
    const signal = signalArray[i]
    /**
     * @type {Array<string>}
     */
    const [ signatureResult, secretKeysId ] = signSignature(item, doc, signal)
    if (!signatureResult) return null
    const signature = new Signature({
      type,
      signatory: currentUser,
      secretKeysId,
      content: signatureResult,
      verify: 1
    })
    signatureArray.push(signature)
  }
  return signatureArray
}

/**
     * 
     * @param {Item} item 
     * @param {Doc} doc 
     * @param {Array<Signature> | null} signature
     * @param {number} offset
     * @param {Array<string>} signalFromOffset
     * @returns {Array<boolean | Promise<boolean>> | boolean}
     */
const verifyItemSignal = (item, doc, signature, offset, signalFromOffset) => {
  if (!signature || !signature.length) return false
  /**
   * @type { Array<boolean | Promise<boolean>>}
   */
  const verifyResult = []
  for (let i = 0; i < item.length; i++) {
    if (i < offset) {
      verifyResult.push(true)
    } else {
      const signal = signalFromOffset[i - offset]
      verifyResult.push(verifySignature(item, doc, signature[i], signal))
    }
  }
  return verifyResult
}

export class BasicSignature {
    /**
     * 从decoder中读取数据
     * @param {any} decoder 
     * @returns {{
     *   id: string,
     *   type: number,
     *   secretKeysId: string,
     *   signatory: string,
     *   content: string,
     *   light: boolean
     * }}
     */
    static fromDecoder(decoder){
      const id = decoding.readVarString(decoder)
      const light = decoding.readUint8(decoder) === 1
      const type = decoding.readUint8(decoder)
      const secretKeysId = decoding.readVarString(decoder)
      const signatory = decoding.readVarString(decoder)
      const content = decoding.readVarString(decoder)
      return {
        id,
        light,
        type,
        secretKeysId,
        signatory,
        content,
      }
    }

    /**
     * 构造方法
     * @param {{
        *   id?: string,  // 签名id
        *   light?: boolean, // 是否为轻签名
        *   type: number, // 签名方法
        *   content: string, // 签名hash
        *   signatory: string, // 签名人
        *   secretKeysId: string, // 签名使用的密钥id
        *   verify?: number, //是否验证成功
        * }} options 
    */
    constructor(options){
      this.id = options.id || uuidv4()
      this.light = options.light || false
      this.type = options.type
      this.secretKeysId = options.secretKeysId
      this.signatory = options.signatory
      this.content = options.content
      this.verify = options.verify ?? 0 // 0 = haven't, 1 = yes, 2 = no
    }

    /**
     * 
     * @param {any} encoder 
     */
    toEncoder (encoder) {
      encoding.writeVarString(encoder, this.id)
      encoding.writeUint8(encoder, this.light ? 1: 0)
      encoding.writeUint8(encoder, this.type)
      encoding.writeVarString(encoder, this.secretKeysId)
      encoding.writeVarString(encoder, this.signatory)
      encoding.writeVarString(encoder, this.content)
    }

    /**
     * 打印signature信息
     * @returns {string}
     */
    toString () {
        return `[签名基础信息-${this.id}-${this.light}-${this.type}-${this.signatory}-${this.secretKeysId}-${this.content}-${this.verify}]`
    }
} 

// 数字签名
export class Signature extends BasicSignature {
    /**
     * 
     * @param {any} decoder 
     * @returns {Signature}
     */
    static fromDecoder(decoder){
      const basicOptions = super.fromDecoder(decoder)
      return new Signature(basicOptions)
    }
  
    /**
     * 
     * @param {Item} item 
     * @param {Doc} doc 
     * @returns {Array<Signature> | null}
     */
    static sign(item, doc){
      // @ts-ignore
      return signItemSignal(item, doc, getItemSignal(item, 'array'))
    }

    /**
     * 
     * @param {Item} item 
     * @param {Doc} doc 
     * @param {Array<Signature> | null} signature
     * @param {number} offset
     * @returns {Array<boolean | Promise<boolean>> | boolean}
     */
    static verify(item, doc, signature, offset){
      /**
       * @type {Array<string>}
       */
      let signalArray = []
      for(let i = offset; i < item.length; i ++){
        // @ts-ignore
        signalArray.push(getItemSignal(item, i))
      }
      return verifyItemSignal(item, doc, signature, offset, signalArray)
    }

    /**
     * 
     * @param {any} encoder 
     */
    toEncoder(encoder){
      super.toEncoder(encoder)
    }

    /**
     * @returns {string}
     */
    toString(){
      return `原子签名-${this.id}-${this.type}-${this.signatory}-${this.secretKeysId}-${this.content}`
    }
}

export class IntegrateSignature extends BasicSignature {
    /**
     * @type {Map<string, IntegrateSignature>}
     */
    static signaturePool = new Map()

    /**
     * 
     * @param {any} decoder 
     * @returns {IntegrateSignature}
     */
    static fromDecoder (decoder) {
      const basicOptions = super.fromDecoder(decoder)
      const len = decoding.readVarUint(decoder)
      /**
       * @type {Array<ID>}
       */
      const ids = []
      for (let i = 0; i < len; i++) {
        const client = decoding.readVarUint(decoder)
        const clock = decoding.readVarUint(decoder)
        const id = new ID(client, clock)
        ids.push(id)
      }
      /**
       * @type {IntegrateSignature | undefined}
       */
      let integrateSignature = IntegrateSignature.signaturePool.get(basicOptions.id)
      if (!integrateSignature) {
        integrateSignature = new IntegrateSignature({
          ...basicOptions,
          ids
        })
        IntegrateSignature.signaturePool.set(basicOptions.id, integrateSignature)
      }
      return integrateSignature
    }

    /**
     * 检测一个聚合签名是否可以绑定给一个item
     * @param {Item} item 
     * @param {IntegrateSignature | null | undefined} signature 
     */
    static isSuitable(item, signature){
      if (!signature) return false
      /**
       * @type {ID}
       */
      const id = item.id
      const len = item.length
      if (!signature.ids) return false
      for (let clock = id.clock; clock < id.clock + len; clock++) {
        if (signature.ids.findIndex(target => target.client === id.client && target.clock === clock) === -1) {
          return false
        }
      }
      return true
    }

    /**
     * 
     * @param {Item} item 
     * @param {Doc} doc 
     * @returns {IntegrateSignature | null}
     */
    static sign(item, doc){
      debugLog('createIntegrateSignature - 开始创建聚合签名')
      if (!doc) return null
      const { currentUser } = doc.signature
      const type = getSignatureLevelConfig(doc, 'signConfig', 'type')
      const bit = getSignatureLevelConfig(doc, 'signConfig', 'bit')
      /**
       * @type {Array<ID>}
       */
      const ids = []
      const id = item.id
      for (let i = id.clock; i < id.clock + item.length; i++) {
        ids.push(new ID(id.client, i))
      }
      const signalString = getItemSignal(item, 'string')
      const secretKeys = getSecretKeys(doc, item, currentUser, type, bit)
      if (!secretKeys) { return null }
      // @ts-ignore
      const [ signatureResult, secretKeysId ] = signSignature(item, doc, signalString)
      return new IntegrateSignature({
        type,
        ids,
        content: signatureResult,
        signatory: currentUser,
        secretKeysId,
        verify: 1
      })
    }

    /**
     * 
     * @param {Item} item 
     * @param {Doc} doc
     * @param {IntegrateSignature | null | undefined} signature
     * @returns {boolean | Promise<boolean>}
     */
    static verify(item, doc, signature){
      if (!signature) return false
      if (signature.verify) {
        const verifyResult = signature.verify === 1
        debugLog('integrate signature验证 - 击中缓存', verifyResult)
        return verifyResult
      }
      /**
       * @type {Array<ID>}
       */
      const ids = signature.ids
      /**
       * @type {string}
       */
      let signal = ''
      for (const id of ids) {
        const structs = doc.store.clients.get(id.client)
        if (structs) {
          let index
          try {
            index = findIndexSS(structs, id.clock)
          } catch {
            // 不一定能找到item，因为这个item可能还没有挂载上来
            // 遇到这种情况，直接验证失败
            return false
          }
          const struct = structs[index]
          if (struct instanceof Item) {
            signal += getItemSignal(struct, id.clock - struct.id.clock)
          }
        }
      }
      const verifyResult = verifySignature(item, doc, signature, signal)
      debugLog('integrate signature验证结果', verifyResult)
      return verifyResult
    }

    /**
     * 构造方法
     * @param {{
     *   id?: string,  // 签名id
     *   type: number, // 签名方法
     *   content: string, // 签名hash
     *   signatory: string, // 签名人
     *   ids: Array<ID> // 签名items IDs
     *   secretKeysId: string, // 签名使用的密钥id
     *   verify?: number, //是否验证成功
     * }} options 
     */
    constructor (options) {
        super(options)
        this.ids = options.ids
    }

    /**
     * 将signature写入encoder编码器
     * @param {any} encoder 
     */
    toEncoder (encoder) {
        super.toEncoder(encoder)
        const len = this.ids.length
        encoding.writeVarUint(encoder, len)
        for (let i = 0; i < len; i++) {
            const id = this.ids[i]
            encoding.writeVarUint(encoder, id.client)
            encoding.writeVarUint(encoder, id.clock)
        }
    }

    getIdsByString(){
        /**
         * @type {string}
         */
        let ids_str = ''
        const len = this.ids.length
        for (let i = 0; i < len; i++) {
            const id = this.ids[i]
            ids_str += `[ client: ${id.client}, clock: ${id.clock} ]`
            if(i < len) ids_str += ', '
        }
        return ids_str
    }
    
    /**
     * 打印signature信息
     * @returns {string}
     */
    toString () {
        return '聚合签名 - ' + super.toString() + ' - 签名IDS - ' + this.getIdsByString()
    }
}

export class DeleteSignature extends Signature{
    /**
     * 
     * @param {any} decoder 
     * @returns {DeleteSignature}
     */
    static fromDecoder(decoder){
      const basicOptions = BasicSignature.fromDecoder(decoder)
      return new DeleteSignature(basicOptions)
    }

    /**
     * 
     * @param {Item} item 
     * @param {Doc} doc 
     * @returns {Array<Signature> | null}
     */
    static sign(item, doc){
      // @ts-ignore
      return signItemSignal(item, doc, getItemSignal(item, 'array', false))
    }

    /**
     * 
     * @param {Item} item 
     * @param {Doc} doc 
     * @param {Array<Signature>} signature
     * @param {number} offset
     * @returns {Array<boolean | Promise<boolean>> | boolean}
     */
    static verify(item, doc, signature, offset){
      /**
       * @type {Array<string>}
       */
      let signalArray = []
      for(let i = offset; i < item.length; i ++){
        // @ts-ignore
        signalArray.push(getItemSignal(item, i, false))
      }
      return verifyItemSignal(item, doc, signature, offset, signalArray)
    }

    /**
     * 
     * @param {any} encoder 
     */
    toEncoder(encoder){
      super.toEncoder(encoder)
    }

    /**
     * @returns {string}
     */
    toString(){
      return `删除签名-${this.id}-${this.type}-${this.signatory}-${this.secretKeysId}-${this.content}`
    }
}