const axios = require("axios");
const ClientWarapper = require("kaspa-rpc-client").ClientWrapper

class BaseRPCNode {
  constructor(NODE_OPTION) {
    const { rpcUser, rpcHost, rpcPassword, rpcPort } = NODE_OPTION;
    const request_Rpc = axios.create({
      baseURL: `http://${rpcHost}:${rpcPort}`, // 修正为 baseURL
      auth: {
        username: rpcUser,
        password: rpcPassword,
      },
      timeout: 5000, // 超时时间,5秒
    });
    this.request_Rpc = request_Rpc;
  }
  async callRpcMethod(method, params = []) {
    try {
      const response = await this.request_Rpc.post("/", {
        jsonrpc: "1.0",
        id: "testnet",
        method: method,
        params: params,
      });
      return response.data.result;
    } catch (error) {
      console.error("RPC Error:", error.response ? error.response.data : error.message);
      throw error;
    }
  }
  async getblockcount() {
    try {
      // const blockhash = await getblockhash(height);
      const data = await this.callRpcMethod("getblockcount", []);
      return data;
    } catch (err) {
      throw err;
    }
  }
  async getblockhash(height) {
    try {
      const data = await this.callRpcMethod("getblockhash", [height]);
      return data;
    } catch (err) {
      throw err;
    }
  }
  async getblock(param) {
    try {
      let data;
      if (typeof param === "string") {
        data = await this.callRpcMethod("getblock", [param, 2]);
      } else if (typeof param === "number") {
        const hash = await this.getblockhash(param);
        data = await this.callRpcMethod("getblock", [hash, 2]);
      }
      return data;
    } catch (err) {
      throw err;
    }
  }
}

class NEXARPCNode extends BaseRPCNode {
  constructor(NODE_OPTION) {
    super(NODE_OPTION); // 调用父类构造函数
  }

  async verify_wallet(address) {
    try {
      const data = await this.callRpcMethod("getreceivedbyaddress", [address]);
      return true;
    } catch (err) {
      return false;
    }
  }

  async getblock(height) {
    try {
      // const blockhash = await getblockhash(height);
      const data = await this.callRpcMethod("getblock", [height, 2]);
      return data;
    } catch (err) {
      throw err;
    }
  }
  /**
   * 校验是否是本矿池报块
   * @param {Number} height 报块高度
   * @param {String} address 矿池报块钱包地址
   * @returns
   */
  async verify_block(height, address) {
    try {
      const block_data = await this.getblock(height);
      const { tx } = block_data;
      for (let item of tx) {
        if (item.vin.length === 0) {
          const { addresses } = item.vout[0].scriptPubKey;
          if (address === addresses[0]) {
            return block_data;
          } else {
            return false;
          }
        }
      }
    } catch (err) {
      throw err;
    }
  }

  block(block_data) {
    try {
      // const block_data = await this.getblock(height);
      const { tx, time, hash, height } = block_data;
      let block_fees = 0;
      let block_reward = 0;
      for (let item of tx) {
        if (item.vin.length === 0) {
          // coinbase交易
          block_reward = item.sends;
        } else {
          block_fees += item.fee;
        }
      }
      const result = { height, hash, time, block_reward, block_fees };
      return result;
    } catch (err) {
      throw err;
    }
  }
}

class GRSRPCNode extends BaseRPCNode {
  constructor(NODE_OPTION) {
    super(NODE_OPTION); // 调用父类构造函数
  }
  async verify_block(_height, REPORT_ADDRESS) {
    try {
      const block_data = await this.getblock(_height);
      const { tx } = block_data;
      for (let item of tx) {
        const { vin, vout } = item;
        if (vin[0].coinbase) {
          for (let value of vout) {
            if (value.scriptPubKey.address) {
              if (value.scriptPubKey.address === REPORT_ADDRESS) {
                return block_data;
              } else {
                return false;
              }
            }
          }
        }
      }
    } catch (err) {
      throw err;
    }
  }

  block(data) {
    const { hash, tx, height, time } = data;
    // const littleEndian = Buffer.from(Buffer.from(hash, "hex").reverse());
    let reward = 0;
    let fees = 0;
    for (let item of tx) {
      const { vin, vout } = item;
      if (vin[0].coinbase) {
        for (let value of vout) {
          reward += value.value;
        }
      } else {
        const { fee } = item;
        fees += fee;
      }
    }
    // console.log(littleEndian.toString("hex")," ", hash);

    return { height, hash, time, block_reward: reward, block_fees: fees };
  }
}

class MONARPCNode extends BaseRPCNode {
  constructor(NODE_OPTION) {
    super(NODE_OPTION); // 调用父类构造函数
  }

  async verify_block(_height, REPORT_ADDRESS) {
    try {
      const block_data = await this.getblock(_height);
      const { tx } = block_data;
      for (let item of tx) {
        const { vin, vout } = item;
        if (vin[0].coinbase) {
          for (let value of vout) {
            if (value.scriptPubKey.addresses) {
              if (value.scriptPubKey.addresses["0"] === REPORT_ADDRESS) {
                return block_data;
              } else {
                return false;
              }
            }
          }
        }
      }
    } catch (err) {
      throw err;
    }
  }

  block(data) {
    const { hash, tx, height, time } = data;

    let reward = 0;
    for (let item of tx) {
      const { vin, vout } = item;
      if (vin[0].coinbase) {
        for (let value of vout) {
          if (value.scriptPubKey.addresses) {
            reward += value.value;
          }
        }
      }
    }
    return { height, hash, time, block_reward: reward, block_fees: null };
  }
}

class DGBRPCNode extends BaseRPCNode {
  constructor(NODE_OPTION) {
    super(NODE_OPTION); // 调用父类构造函数
  }

  async verify_block(_height, REPORT_ADDRESS) {
    try {
      const block_data = await this.getblock(_height);
      const { tx, pow_algo } = block_data;
      // if (algorithm === pow_algo) {
      for (let item of tx) {
        const { vin, vout } = item;
        if (vin[0].coinbase) {
          for (let value of vout) {
            if (value.scriptPubKey.addresses) {
              if (value.scriptPubKey.addresses[0] === REPORT_ADDRESS) {
                return block_data;
              }
            }
          }
        }
        // }
      }
      return false;
    } catch (err) {
      throw err;
    }
  }

  async verify_block_with_alogo(_height, REPORT_ADDRESS, algorithm) {
    try {
      const block_data = await this.getblock(_height);
      const { tx, pow_algo } = block_data;
      if (algorithm === pow_algo) {
        for (let item of tx) {
          const { vin, vout } = item;
          if (vin[0].coinbase) {
            for (let value of vout) {
              if (value.scriptPubKey.addresses) {
                if (value.scriptPubKey.addresses[0] === REPORT_ADDRESS) {
                  return block_data;
                }
              }
            }
          }
        }
      }
      return false;
    } catch (err) {
      throw err;
    }
  }

  block(data) {
    const { hash, tx, height, time } = data;
    let reward = 0;
    for (let item of tx) {
      const { vin, vout } = item;
      if (vin[0].coinbase) {
        for (let value of vout) {
          if (value.scriptPubKey.addresses) {
            reward += value.value;
          }
        }
      }
    }
    return { height, hash, time, block_reward: reward, block_fees: null };
  }
}

class RXDRPCNode extends BaseRPCNode {
  constructor(NODE_OPTION) {
    super(NODE_OPTION);
  }

  async verify_block(_height, REPORT_ADDRESS) {
    try {
      const block_data = await this.getblock(_height);
      const { tx } = block_data;
      for (let { vin, vout } of tx) {
        if (vin[0].coinbase) {
          for (let { scriptPubKey } of vout) {
            if (scriptPubKey.addresses[0] === REPORT_ADDRESS) {
              const hash = await this.getblockhash(_height);
              const blockstats = await this.callRpcMethod("getblockstats", [hash]);
              const { blockhash, height, time, subsidy, totalfee } = blockstats;
              return { height, hash: blockhash, time, block_reward: subsidy + totalfee, block_fees: totalfee };
            }
          }
        }
      }
      return false;
    } catch (err) {
      throw err;
    }
  }

  block(data) {
    return data;
  }
}

class ENXNode {
  constructor(NODE_OPTION) {
    const { rpcHost, rpcPort } = NODE_OPTION;
    this.rpcHost = rpcHost;
    this.rpcPort = rpcPort;

    this.state = false
  }

  async init (){
    try{
      const warapper = new ClientWarapper({
        hosts: [`${this.rpcHost}:${this.rpcPort}`],
        verbose: true
      })
      await warapper.initialize()
      this.client = await warapper.getClient()
      this.state = true
    } catch (err){
      throw err
    }
  }

  async getblockcount(){
    if(!this.state){
      console.log("节点未初始化");
      return
    }
    try{
      const data = await this.client.getBlockDagInfo()
      const {virtualDaaScore} = data
      return Number(virtualDaaScore)
    } catch(err){
      throw err
    }
  }
  /**
   * 
   * @param {Number} start_height 
   * @param {String} REPORT_ADDRESS 
   * @returns 
   * 
{
  address: 'entropyx:qpe7q43ajzuv42az6kjdu84yrzdhzj33w03kpytfg5lx5yk3x0pd6x0r87hhr',
  outpoint: {
    transactionId: '9341ecd11e3cccb06ff09ad8f604aee80f9777aafa0f8858a5501c2736b31cb2',
    index: 0
  },
  utxoEntry: {
    amount: '50000000000',
    scriptPublicKey: {
      version: 0,
      scriptPublicKey: '2073e0563d90b8caaba2d5a4de1ea4189b714a3173e3609169453e6a12d133c2ddac'
    },
    blockDaaScore: '2776996',
    isCoinbase: false
  }
}
   */
  async getTransactions(start_height, REPORT_ADDRESS){
    if(!this.state){
      console.log("节点未初始化");
      return
    }
    try{
      const {entries} = await this.client.getUtxosByAddresses({addresses:[REPORT_ADDRESS]})
      const blocks = []
      for(let item of entries){
        if(Number(item.utxoEntry.blockDaaScore) > start_height && item.utxoEntry.isCoinbase){
          blocks.push({
            height: Number(item.utxoEntry.blockDaaScore),
            amount: Number(item.utxoEntry.amount) / 10 ** 8,
          })
        }
      }
      return blocks
    } catch(err){
      throw err
    }
  }
}



class HttpNode {
  constructor(NODE_OPTION){
    const { rpcHost, rpcPassword, rpcPort } = NODE_OPTION;
    this.host = rpcHost
    this.prot = rpcPort
    this.api_key = rpcPassword
  }

  async Get(api = "", params = {}){
    const url = `http://${this.host}:${this.prot}/${api}`
    try{
      const res = await axios.get(url, { params, headers: { "x-api-key": this.api_key } })
      return res.data
    } catch (err){
      throw err
    }
  }

  async Post(api = "", params = {}){
    const url = `http://${this.host}:${this.prot}/${api}`
    try{
      const res = await axios.post(url, params, { headers: { "x-api-key": this.api_key } })
      return res.data
    } catch (err){
      throw err
    }
  }
}

class ALPHRPCNode extends HttpNode {
  constructor(NODE_OPTION) {
    super(NODE_OPTION);
  }

  async getblockcount(){
    try{
      const result = await this.Get("blockflow/chain-info", {fromGroup:0, toGroup:0})
      return result.currentHeight
    } catch(err){
      throw err
    }
  }

  async getblockhash(height){
    try{
      const result = await this.Get("blockflow/hashes", {fromGroup:0, toGroup:0, height})
      return result.headers[0]
    } catch(err){
      throw err
    }
  }

  async getblock(block_hash){
    try{
      const result = await this.Get(`blockflow/blocks/${block_hash}`)
      return result
    } catch(err){
      throw err
    }
  }

  async verify_block(height, REPORT_ADDRESS){
    try{
      const block_hash = await this.getblockhash(height)
      const block = await this.getblock(block_hash)
      const {transactions, timestamp} = block
      const coinbaseTx = transactions[transactions.length - 1].unsigned
      if (coinbaseTx.inputs.length === 0) {
        const {fixedOutputs} = coinbaseTx
        if(fixedOutputs.length === 1){
          const {attoAlphAmount, address } = fixedOutputs[0]
          if(address === REPORT_ADDRESS){
            return {height, hash: block_hash, time:Math.trunc(timestamp / 1000), block_reward: Number(attoAlphAmount), block_fees: null };
          }
        }
      }
      return false
    } catch(err){
      throw err
    }
  }

  block(data){
    return data
  }
}


module.exports = {
  NEXARPCNode,
  GRSRPCNode,
  MONARPCNode,
  DGBRPCNode,
  RXDRPCNode,
  ENXNode,
  ALPHRPCNode
};