import { debugLog, Doc, GC, Item, Skip, UpdateEncoderV1, YArray, YMap } from "../../internals"
import { BasicSignature } from './signature'
import crypto from "crypto"

/**
 * @type {Map<string, Doc | null>}
 */
const spaceDocMap = new Map()

/**
 * 获取一个Y.Doc的spaceDoc
 * @param {Doc | null | undefined} doc 
 * @returns {Doc | null | undefined}
 */
export const getSpaceDoc = (doc) => {
    if (!doc) return null
    const spaceDocInMap = spaceDocMap.get(doc.guid)
    if (spaceDocInMap) {
      return spaceDocInMap
    }
    if (doc.spaceDoc) {
      return doc
    } else {
      const spaceDoc = getSpaceDoc(doc.parentDoc)
      if (spaceDoc && !spaceDocMap.get(doc.guid)) {
        spaceDocMap.set(doc.guid, spaceDoc)
      }
      return spaceDoc
    }
}

/**
 * 检测一个对象是否是纯对象{}或new Object()
 * @param {any} obj 
 * @returns 
 */
export const isPlainObject = (obj) => {
    return Object.prototype.toString.call(obj) === '[object Object]'
}

/**
 * 
 * @param {{
 *  [key: string] : any
 * } | null | undefined} signatureConfig 
 */
export const combineDocSignatureConfig = (signatureConfig, anotherSignatureConfig = getDefaultDocSignatureConfig()) => {
    if(!signatureConfig){
        return anotherSignatureConfig
    }
    const keys = Object.keys(anotherSignatureConfig)
    keys.forEach(key => {
        const defaultSignatureConfigValue = anotherSignatureConfig[key]
        const signatureConfigValue = signatureConfig[key]
        if(typeof signatureConfigValue === 'undefined'){
            signatureConfig[key] = anotherSignatureConfig[key]
        } else {
            if(isPlainObject(defaultSignatureConfigValue) && isPlainObject(signatureConfigValue)){
                signatureConfig[key] = combineDocSignatureConfig(signatureConfig[key], anotherSignatureConfig[key])
            }
        }
    })
    return signatureConfig
}

/**
 * 
 * @returns {{
 *  [key: string]: any
 * }}
 */
export const getDefaultDocSignatureConfig = () => {
    return {
      currentUser: '', // 当前连接对应的用户，多个clientID可能对应同个用户
      checkSource: {  // 哪些签名来源需要进行校验
        local: false, // 来自local的item，如执行操作得到的item
        decoder: false, // 来自远程同步得到的item
        level: false // 来自levelDB的item
      },
      checkType: { // 哪些签名的类型需要进行校验
        atom: false, // 原子签名校验
        aggregate: false, // 聚合签名校验
        delete: false // 删除签名校验
      },
      signConfig: {
        light: false, // 轻签名，不签内容
        type: 1, // 默认使用ecdsa签名算法
        bit: 512, // 默认使用的签名位数，对RSA签名算法有效
        webAssembly: false // 开启webAssembly，会使用webAssembly版本的加密解密库进行签名工作
      },
      verifyConfig: {
          checkPriority: {
            integrate: false,
            delete: true      
          },
          light: false, // 轻验证，不验内容
          lazyCheck: false, // 懒验证，签名验证在线程空闲时进行
          webWorker: false, // 开启webWorker，会将验证工作放入webWorker中执行 
          webAssembly: false // 开启webAssembly，会使用webAssembly版本的加密解密库进行验证工作
        },
      secretKeysDoc: null // 存储用户密钥对的doc
    }
}

/**
 * 获取签名配置的某一个下层级的配置，如signature.checkType.atom
 * @param {Doc} doc 
 * @param {string} levelOne
 * @param {string} levelTwo
 * @param {string | undefined} levelThree
 * @returns 
 */
export const getSignatureLevelConfig = (doc, levelOne, levelTwo, levelThree = undefined) => {
    let levelOneObj
    if(levelThree !== undefined){
        levelOneObj = doc.signature[levelOne][levelTwo]
        levelTwo = levelThree
    } else {
        levelOneObj = doc.signature[levelOne]
    }
    return typeof levelOneObj[levelTwo] === 'function' ? levelOneObj[levelTwo](doc) : levelOneObj[levelTwo]
}

/**
 * 获取一个Y.Doc的spaceDoc
 * @param {Doc | null | undefined} doc 
 * @returns {{
 *   [key: string]: any
 * }}
 */
export const getSpaceDocSignatureConfig = (doc) => {
    const spaceDoc = getSpaceDoc(doc)
    if(!spaceDoc){
        debugLog('getSpaceDocSignatureConfig failed: no spaceDoc found.')
        return {}
    }
    return spaceDoc.signature || {}
}

/**
 * 获取Item的某个offset位置的signal
 * @param {Item} item 
 * @param {number} offset 
 * @returns {string}
 */
export const getItemOffsetSignal = (item, offset = 0, needContent = true) => {
    const encoder = new UpdateEncoderV1()
    item.signal(encoder, offset, needContent)
    // item.write(encoder, offset, true)
    const target = JSON.stringify(encoder.toUint8Array())
    return crypto.createHash("sha256").update(target).digest('base64')
}

/**
 * 获取Item的某个offset的原子Item的hash
 * @param {Item | GC | Skip} item 
 * @param {number} offset 
 * @returns {string}
 */
export const getAtomicItemHash = (item, offset = 0) => {
    const encoder = new UpdateEncoderV1()
    item.write(encoder, offset, true)
    const target = JSON.stringify(encoder.toUint8Array())
    return crypto.createHash("sha256").update(target).digest('base64')
}

/**
 * 获取Item的所有原子Item的hash
 * @param {Item | GC } item
 * @returns {Array<string>}
 */
export const getAllAtomicItemHash = (item) => {
    const hashs = []
    if(item instanceof Item){
        for(let offset = 0 ; offset < item.length ; offset ++){
            hashs.push(getAtomicItemHash(item, offset))
        }
    }
    return hashs
}

/**
 * 获取Item的signal
 * @param {Item} item 
 * @param {number | undefined | 'array' | 'string'} offset 
 * @returns {Array<string> | string}
 */
export const getItemSignal = (item, offset = 'array', needContent = true) => {
    if (typeof offset === 'number') {
        return getItemOffsetSignal(item, offset, needContent)
    }
    /**
     * @type {Array<string> | string}
     */
    let signalArray = offset === 'array' ? [] : ''
    const len = item.length
    for (let i = 0; i < len; i++) {
        if(typeof signalArray === 'string'){
            signalArray += getItemOffsetSignal(item, i, needContent)
        } else {
            signalArray.push(getItemOffsetSignal(item, i, needContent))
        }
    }
    return signalArray
}

/**
 * 
 * @param {Item} item 
 * @returns 
 */
export const isAtomicItem = (item) => {
    return item.length === 1
}

/**
 * 
 * @param {Doc} doc 
 * @param {Item} item 
 * @param {string} signatory
 * @param {number} type 
 * @param {number | undefined} bit 
 * @param {string | undefined} secretKeysId
 * @returns {YMap<any> | null}
 */
export const getSecretKeys = (doc, item, signatory, type, bit = undefined, secretKeysId = undefined) => {
    const signatureConfig = doc.signature
    // @ts-ignore
    const { secretKeysDoc } = signatureConfig
    if (!secretKeysDoc){
        debugLog('getSecretKeys failed: no secretKeysDoc found.', doc, item, signatory, type, bit, secretKeysId)
        return null
    }
    /**
     * @type {YArray<YArray<any>>}
     */
    const mySecretKeys = secretKeysDoc.getMap().get(signatory)
    if (!mySecretKeys){
        debugLog("getSecretKeys failed: not signatory's secretKeys found.", doc, item, signatory, type, bit, secretKeysId)
        return null
    }

    const typeSecretKeys = mySecretKeys.get(type)
    /**
     * @type {Array<any>}
     */
    const typeSecretKeysArray = typeSecretKeys.toJSON()

    /**
     * @type {number}
     */
    let index = -1
    if (typeof secretKeysId !== 'undefined') {
      index = typeSecretKeysArray.findIndex(item => item.id === secretKeysId)
    } else if (typeof bit !== 'undefined') {
      index = type === 0 ? typeSecretKeysArray.findIndex(item => item.bit === bit) : typeSecretKeysArray.length - 1
    } else {
      index = typeSecretKeysArray.length - 1
    }

    if (index < 0){
        debugLog('getSecretKeys failed: no specific secretKeysPair found.', doc, item, signatory, type, bit, secretKeysId)
        return null
    }

    /**
     * @type {YMap<any>}
     */
    const secretKeys = typeSecretKeys.get(index)
    return secretKeys
}

/**
 * 
 * @param {BasicSignature} signature 
 * @param {boolean} verifyResult 
 */
export const changeSignatureVerify = (signature, verifyResult) => {
    signature.verify = verifyResult ? 1 : 2
    if (!verifyResult) {
      // console.log('failed one')
    }
}


/**
 * 
 * @param {Doc} doc 
 */
const getHistoryLen = (doc) => {
    const store = doc.store
    const clients = store.clients.keys()
    // 计算长度
    let len = 0
    for(let client of clients){
        const clientStores = store.clients.get(client)
        if(clientStores){
            len += clientStores.length
        }
    }
     return len
}

/**
 * 
 * @param {Doc} doc 
 * @param {number} retStep 
 * @param {(data: {
 *   [key: string]: any
 * }) => void} progressCallback 
 */
export const signDoc = (doc, retStep, progressCallback) => {
    // 计算历史长度
    let len = getHistoryLen(doc)
    let lastRate = 0 // 上次反馈时的比率
    let current = 0

    progressCallback({
        status: 'initial',
        progress: 0,
        all: len,
        rate: 0,
    })

    const store = doc.store
    const clients = store.clients.keys()
    for(let client of clients){
        const clientStores = store.clients.get(client)
        if(clientStores){
            for(let item of clientStores){
                current ++
                if(item instanceof Item){
                    item.sign('local', 0, [ 'atom', 'aggregate', 'delete' ])
                }
                const rate = current / len
                if( rate - lastRate >= retStep * 0.01 ){
                    progressCallback({
                        status: 'pending',
                        progress: current,
                        all: len,
                        rate,
                    })
                    lastRate = rate
                }
            }
        }
    }

    progressCallback({
        status: 'done',
        all: len
    })
}