

# from web3 import AsyncWeb3, AsyncHTTPProvider, Web3
# from web3.middleware import async_geth_poa_middleware
# import web3
import requests
import os
import datetime
import time
import math
import json
import pathlib
import shutil
from collections.abc import Iterable
import decimal
import traceback
import pandas as pd
import sys
import numpy as np
import copy
import asyncio
import aiohttp
 
 

class BaseVariableFunction:
    def __init__(self, scriptfile):
        # 获取当前脚本的名字
        self.scriptfilename = pathlib.Path(scriptfile).name
        # 获取脚本所在目录的绝对路径
        self.script_directory = pathlib.Path(scriptfile).resolve().parent
        self.src_directory = self.script_directory.parent
        # 获取项目所在目录的绝对路径
        self.project_directory = self.src_directory.parent
        self.code_path = self.project_directory / 'src/code/'
        self.librarydata_path = self.project_directory / 'src/librarydata/'
        self.library_path = self.project_directory / 'src/library/'
   

        self.ori_contracttokentx_path = self.librarydata_path / 'ori_contracttokentx/'
        self.contracttokentx_path = self.librarydata_path / 'contracttokentx/'
        self.base_library_file = self.code_path / 'base_library.json'
        self.dalao_buysell_count_path = self.librarydata_path / 'dalao_buysell_count/'
        self.dalao_merge_path = self.librarydata_path / 'dalao_merge/'
   

     
        
        
        self.contract_tokentx_columns =          ["blockNumber", "timeStamp", "hash","from","to","value"]
        

        self.baseurl ="https://api.etherscan.io/api"
        self.httpProvider_arr= [
        
        "https://ethereum-rpc.publicnode.com",
        "https://ethereum.publicnode.com",
        "https://mainnet.gateway.tenderl",
            "https://cloudflare-eth.com",
            "https://main-light.eth.linkpool.io", 
            "https://ethereumnodelight.app.runonflux.io",
            "https://rpc.ankr.com/eth",
            "https://eth.llamarpc.com",
             "https://api.mycryptoapi.com/eth",
            "https://mainnet.eth.cloud.ava.do/",
            "https://eth.merkle.io",
            "https://go.getblock.io/aefd01aa907c4805ba3c00a9e5b48c6b",
            "https://eth.drpc.org",
            "https://api.tatum.io/v3/blockchain/node/ethereum-mainnet",
            "https://eth.meowrpc.com",
            "https://1rpc.io/eth",
            "https://eth.api.onfinality.io/public",
            "https://ethereum.blockpi.network/v1/rpc/public",
            
            "https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161",
            "https://rpc.ankr.com/eth",
            "https://eth.rpc.blxrbdn.com",
    
 
            
        ]
 
    def makedirpath(self, folder):
        pathlib.Path(folder).mkdir(exist_ok=True)

    def rmfolder(self, folder):
        if pathlib.Path(folder).exists():
            shutil.rmtree(folder)

    def open_base_library(self):

        with self.base_library_file.open(mode='r', encoding='utf-8') as f:
            self.base_library = json.load(f)
        return self.base_library

    def open_singledalao_txhash_library(self):

        with self.base_singledalao_txhash_library.open(mode='r', encoding='utf-8') as f:
            self.singledalao_txhash_library = json.load(f)
        return self.singledalao_txhash_library

    def get_current_timestamp(self):

        timestamp = math.floor(time.time())
        return timestamp

    def ISO8601_str_to_timestamp(self, datetime_str):
        # 将'2023-08-28T12:43:52Z' 结构的str 转化为utc的时间戳(单位:秒)
        # Define the format of the input datetime string
        # Adjust the format as per your input datetime string
        format_str = '%Y-%m-%dT%H:%M:%SZ'
        # 解析'2023-08-12T22:22:50Z' 并转换为0时区的时间
        dt = datetime.datetime.strptime(datetime_str, format_str).replace(
            tzinfo=datetime.timezone.utc)
        # Convert the datetime object to a timestamp (number of seconds since the epoch)
        timestamp = int(dt.timestamp())
        return timestamp

    def get_blocknumber_bytimestamp_bybscscan(self, timestamp):
        baseurl = "https://api.etherscan.io/api"
        request_getblock_params = {
            "module": "block",
            "action": "getblocknobytime",
            "timestamp": timestamp,
            "closest": "before",
            "apikey":  "1343VMST2JRK8P39A88KU8E97VGY36WFDI"
        }
        retrytime = 5
        blocknumber = 999999999
        while retrytime > 0:
            retrytime -= 1
            req_response = requests.get(
                baseurl, request_getblock_params, timeout=20)

            if req_response.status_code != 200:
                continue
            response = json.loads(
                req_response.text)
            blocknumber = int(response['result'])
            return blocknumber
        raise Exception('get_blocknumber_bytimestamp_bybscscan failed')

    def get_blocknumber_byisostr_bybscscan(self, isostr=None):
        baseurl = "https://api.etherscan.io/api"
        if isostr is None:
            raise Exception('isostr is None failed')
        isostr_timeStamp = self.ISO8601_str_to_timestamp(isostr)
        timestamp = isostr_timeStamp

        request_getblock_params = {
            "module": "block",
            "action": "getblocknobytime",
            "timestamp": timestamp,
            "closest": "before",
            "apikey":  "1343VMST2JRK8P39A88KU8E97VGY36WFDI"
        }
        retrytime = 5
        endblock = 999999999
        while retrytime > 0:
            retrytime -= 1
            req_response = requests.get(
                baseurl, request_getblock_params, timeout=20)

            if req_response.status_code != 200:
                continue
            response = json.loads(
                req_response.text)
            endblock = int(response['result'])
            return endblock
        raise Exception('get_now_blocknumber_bybscscan failed')
    # def init_req_dict(param_sort="asc",
    # offsettimes_max=1,
    # action=None,
    # actiontype=None,
    #  dict_length=15,
    #  remainder_retry_times_max=3,
    #  startblock=27000000, endblock=99900000, timeout=30):

    #  一个dict 有多个配置 然后含有req的params
    def init_req_dict(self,   offsettimes_max=10, key_idx=None, address=None, action=None, actiontype=None,  apikey=None, remainder_retry_times_max=5, startblock=27000000, endblock=99000000, timeout=30 ,file_key="0"):
        # 一个dict 对应一个address 的某一个block范围 一次性的，用完如果endblock>startblock 就remove 这个dict  如果一个address 有两个block range 那么会有两个dict
        # requests_dict key 為空列表 ，使用append 進行添加
        # requests_dict 進行初始化定義
        # timeout ,請求request 的超時時間
        # startblock 初始化以及每次reset重置的startblock
        # remainder_retry_times_max 初始以及重置的初始 可以重傳次數
        # tokencontract_params_list 請求bscscan api的參數設置
        # offsettimes_max  允許的 offset 最大次數，默認一次offset 請求10000條
        # req_response 接受響應列表
        # offsettimes  記錄此add的offset 次數，等於offsettimes_max 就要停止報錯
        # need_remove 此項的響應是否處理完成，如果處理完成(多次超時報錯或數據不對，或者分析完成)，為True，
        #   可以刪除，相應的tokencontracts 列表中的tradersadds 也需要remove此項索引被其他未完成的覆蓋
        # remainder_retry_times 出現超時或請求錯誤可以進行重傳，此爲還可以重傳的次數
        # key_idx 由于是异步执行函数 所有dict 会放在一dict 中 key_idx 就是当前dict在总的dict的索引当前dict 完成，就可以在总dict删除这个索引
        if action is None or actiontype is None:
            raise Exception("not set action")
        if address is None:
            raise Exception("not set address")
        if key_idx is None:
            raise Exception("not set key_idx")

        requests_dict = {}

        
        requests_dict["file_key"] = file_key
        requests_dict["key_idx"] = key_idx
        requests_dict["action"] = action
        requests_dict["actiontype"] = actiontype
        requests_dict["address"] = address
        requests_dict["timeout"] = timeout
        requests_dict["startblock"] = startblock
        requests_dict["endblock"] = endblock
        requests_dict["remainder_retry_times_max"] = remainder_retry_times_max
        requests_dict["offsettimes_max"] = offsettimes_max
        # 上面部分是无论发送了多少次请求都不变得
        # 正在请求中 isrequseting=True 避免之前的没有结果，有发起新的请求
        requests_dict["isrequseting"] = False
        requests_dict["req_params"] = {}
        requests_dict["req_response"] = None
        requests_dict["req_pd"] = None
        requests_dict["offsettimes"] = 0
        requests_dict["need_remove"] = False
        requests_dict["isfirstreq"] = True
        requests_dict["remainder_retry_times"] = remainder_retry_times_max

 
        if action in ["tokentx"] and actiontype in ["contractaddress"]:

            requests_dict["req_params"] = {
                "module": "account",
                "action": action,
                "contractaddress": address,
                "page": 1,
                "offset": 10000,
                "startblock": startblock,
                "endblock": endblock,
                "sort": "asc",
                "apikey": apikey
            }
 

        return requests_dict

    def isnulldf(self, df, add="", Isprint=False):
        if len(df.index) == 0:
            if Isprint:
                print(f"{add} isnulldf\n", end='')
            return True
        else:
            return False

    def drop_confirmations_other_col(self, df, action, actiontype):
     
        if action in ["tokentx"] and actiontype in ["contractaddress"]:
            df = df[ self.contract_tokentx_columns]

        return df

    def fir_handle_decivalue_confirmations_appdropcol_df(self, df, action, actiontype):
        # 首先去除部分列并将timeStamp 转为date形式
        # df = df.dropna()
        # df = df.reset_index(drop=True)
        df = self.drop_confirmations_other_col(
            df=df, action=action, actiontype=actiontype)
        if self.isnulldf(df):
            print(
                f"{action}  fir_handle_decivalue_confirmations_appdropcol_df_() is nulldf")
            return False, None
        return True, df

    def judge_req_completed(self, requests_dict, action, key_idx, cur_conadd, req_dict_total):
        if requests_dict["req_params"]["startblock"] > requests_dict["endblock"]:
            # 如果接下来的block 范围有问题 startblcok >endblock 说明已经获取完毕 ，不用再获取了
            print(
                f"{cur_conadd} startblcok >endblock 结束\n", end='')
            requests_dict["need_remove"] = True
            if req_dict_total.get(key_idx) is not None:
                print(f"remove {action} {cur_conadd} {key_idx}\n", end='')
                del req_dict_total[key_idx]

 
 
  
    def get_cur_conadd(self, requests_dict, action, actiontype):
 
        if action in ["tokentx"] and actiontype in ["contractaddress"]:
            cur_conadd = requests_dict["req_params"]["contractaddress"]
     
    
        return cur_conadd

    def savedfblock(self, cur_conadd, add_df, cur_file_df, requests_dict, write_tokens_path_name, cur_minblock,  cur_maxblock, loop):
        cur_file_df["blockNumber"] = cur_file_df["blockNumber"].astype(
            int)
        cur_file_df_minblock = cur_file_df["blockNumber"].min(
        )
        cur_file_df_maxblock = cur_file_df["blockNumber"].max(
        )
        print(f"cur_conadd={cur_conadd} cur_minblock={cur_minblock} cur_maxblock={cur_maxblock} cur_file_minblock={cur_file_df_minblock} cur_file_maxblock={cur_file_df_maxblock}\n", end='')
        if cur_minblock >= cur_file_df_minblock and cur_maxblock <= cur_file_df_maxblock:
            # 已经全包含了 这个add_df 不用to_csv了
            print(f"loop={loop} {cur_conadd} 都包含\n", end='')
            requests_dict["req_params"]["startblock"] = int(
                cur_file_df_maxblock+1)
        elif cur_minblock >= cur_file_df_minblock and cur_minblock <= cur_file_df_maxblock and cur_maxblock > cur_file_df_maxblock:
            # minblock的部分在csvfile有了  最大block部分没有
            print(f"loop={loop} {cur_conadd} 最小部分包含 最大没有\n", end='')
            add_df = add_df[add_df["blockNumber"] > cur_file_df_maxblock].reset_index(
                drop=True)
            add_df.to_csv(
                write_tokens_path_name,  mode='a', header=False, index=False)
            requests_dict["req_params"]["startblock"] = int(
                cur_maxblock+1)
        elif cur_minblock < cur_file_df_minblock and cur_maxblock >= cur_file_df_minblock and cur_maxblock <= cur_file_df_maxblock:
            # maxblock部分已经在csvfile有了，minblock 没有
            print(f"loop={loop} {cur_conadd} 最大部分包含 最小没有\n", end='')
            add_df = add_df[add_df["blockNumber"] < cur_file_df_minblock].reset_index(
                drop=True)
            add_df.to_csv(
                write_tokens_path_name,  mode='a', header=False, index=False)
            requests_dict["req_params"]["startblock"] = int(
                cur_file_df_maxblock+1)
        elif cur_maxblock < cur_file_df_minblock or cur_minblock > cur_file_df_maxblock:
            # 所有部分不在cssvfile
            print(f"loop={loop} {cur_conadd} 都不包含\n", end='')
            add_df.to_csv(
                write_tokens_path_name,  mode='a', header=False, index=False)
            requests_dict["req_params"]["startblock"] = int(
                cur_maxblock+1)
        else:
            # 其余未细分的，先放着
            print(f"loop={loop} {cur_conadd} 其余部分\n", end='')
            add_df.to_csv(
                write_tokens_path_name,  mode='a', header=False, index=False)
            requests_dict["req_params"]["startblock"] = int(
                cur_maxblock+1)
        print(
            f"aft startblock={requests_dict['req_params']['startblock']}\n", end='')
        return requests_dict

    def get_write_conadd_path_file(self, cur_conadd, write_tokens_path, action, actiontype, filename):
        write_tokens_path_name = None
  
        if action in ["tokentx"] and actiontype in ["contractaddress"]:
            # 如果保留match_address_index_dic 可以用{match_address_index_dic.get(cur_conadd)}进行表示
            write_tokens_path_name = write_tokens_path / \
                f"{filename}.csv"
 
        return write_tokens_path_name

    def handle_IF_tokenresponse_NOTOK(self, tokenresponse, action, cur_conadd, key_idx, requests_dict, req_dict_total):
        # 對於相應正常，結果不正常的，可能是沒有交易記錄 可能是訪問頻率過高等，
        # 對於部分不OK的情況，可以分別處理
        # { "status": "0", "message": "No transactions found", "result": [] }
        # { "status": "0", "message": "NOTOK", "result": "Max rate limit reached, please use API Key for higher rate limit" }
        # { "status": "0", "message": "NOTOK", "result": "Contract source code not verified" }
        # {'status': '0', 'message': 'NOTOK', 'result': 'Error! Invalid contract address format'}
        # {'status': '0', 'message': 'Query Timeout occured. Please select a smaller result dataset',result': None}
        print(
            f"tokenresponse message NOTOK {action} {cur_conadd} \n", "tokenresponse=", tokenresponse, '\n', end='')
        # print("test tokenresponse",
        #       tokenresponse["message"], tokenresponse["result"])
        if (tokenresponse["message"] == "NOTOK"
                and "Max rate limit reached" in tokenresponse["result"]):
            requests_dict["need_remove"] = False
            requests_dict['isrequseting'] = False
            return
  
        elif (tokenresponse["message"] == "No transactions found"):
            requests_dict["need_remove"] = True
            if req_dict_total.get(key_idx) is not None:
                print(f"remove {action} {cur_conadd} {key_idx}\n", end='')
                del req_dict_total[key_idx]
            return
 
 

        else:
            raise Exception(
                'tokenresponse message NOTOK And No matched method')

 
 