import binascii
import rlp
from ethereum import utils
from ethereum.utils import ecsign, normalize_key, int_to_big_endian, checksum_encode, privtoaddr
from solc import compile_files
from web3 import Web3, HTTPProvider
from ethereum.transactions import Transaction





def hander_contract_args(args):
    tmp_list = []
    for a in args:
        tmp_bytearray = bytearray(32)
        assert isinstance(a,str)
        if a[:2] == "0x":
            a = a[2:]
        len_of_a = len(bytes.fromhex(a))
        tmp_bytearray[-len_of_a:] = bytes.fromhex(a)

        tmp_list.append(tmp_bytearray.hex())

    return "".join(tmp_list)

class Client(object):

    def __init__(self, eth_url):
        self.web3 = Web3(HTTPProvider(eth_url))

    def get_privtKey_from_keystore(self,filename, password):
        with open(filename) as keyfile:
            encrypted_key = keyfile.read()
            private_key = self.web3.eth.account.decrypt(encrypted_key, password)
            print(private_key)
            return binascii.hexlify(private_key).decode()

    def transfer_erc20(self,addressFrom,addressTo,value,privtKey):
        '''
        erc20转账
        '''
        contract_instance = self.get_contract_instance(CONTRACT_ADDRESS, CONTRACT_ABI)
        tx = contract_instance.functions.transfer(
            checksum_encode(addressTo),
            int(value*(10**8))
        ).buildTransaction({
            'gasPrice': self.web3.eth.gasPrice,
            'nonce': self.web3.eth.getTransactionCount(checksum_encode(addressFrom)),
        })

        signed = self.web3.eth.account.signTransaction(tx, privtKey)
        tx_id=self.web3.eth.sendRawTransaction(signed.rawTransaction)

        return binascii.hexlify(tx_id).decode()


    def transfer_eth(self,addressFrom, addressTo, value, privtKey, data=b'', startgas=21000):
        '''
        eth转账
        '''
        tx = Transaction(
            nonce=self.web3.eth.getTransactionCount(addressFrom),
            gasprice=self.web3.eth.gasPrice,
            startgas=startgas,
            to=addressTo,
            value=value,
            data=data
        )

        tx.sign(privtKey)
        raw_tx = self.web3.toHex(rlp.encode(tx))
        tx_id = self.web3.eth.sendRawTransaction(raw_tx)
        return "0x" + binascii.hexlify(tx_id).decode()


    def construct_eth_tx(self, addressFrom, addressTo, value, gasLimit=21000,gasPrice=None):
        '''
        构造eth未签名交易
        '''
        tx = Transaction(
            nonce=self.web3.eth.getTransactionCount(addressFrom),
            gasprice=self.web3.eth.gasPrice if not gasPrice else gasPrice,
            startgas=gasLimit,
            to=addressTo,
            value=int(value*(10**18)),
            data=b''
        )
        UnsignedTransaction = Transaction.exclude(['v', 'r', 's'])
        unsigned_tx = rlp.encode(tx, UnsignedTransaction)
        before_hash = utils.sha3(unsigned_tx)
        return binascii.hexlify(unsigned_tx).decode(),binascii.hexlify(before_hash).decode()

    def construct_erc20_tx(self,addressFrom,addressTo,value,gasPrice=None):
        '''
        构造eth未签名交易
        '''
        contract_instance=self.get_contract_instance(CONTRACT_ADDRESS, CONTRACT_ABI)
        tx_dict = contract_instance.functions.transfer(
            checksum_encode(addressTo),
            int(value*(10**8))
        ).buildTransaction({
            'from': addressFrom,
            'nonce': self.web3.eth.getTransactionCount(addressFrom),
        })

        tx = Transaction(
            nonce=tx_dict.get("nonce"),
            gasprice=tx_dict.get("gasPrice") if not gasPrice else gasPrice,
            startgas=tx_dict.get("gas"),
            to=tx_dict.get("to"),
            value=tx_dict.get("value"),
            data=binascii.unhexlify(tx_dict.get("data")[2:]))

        UnsignedTransaction = Transaction.exclude(['v', 'r', 's'])
        unsigned_tx = rlp.encode(tx, UnsignedTransaction)
        before_hash = utils.sha3(unsigned_tx)
        return binascii.hexlify(unsigned_tx).decode(),binascii.hexlify(before_hash).decode()


    def sign(self, unsigned_tx, privtKey):
        '''
        对交易签名
        '''
        before_hash = utils.sha3(binascii.unhexlify(unsigned_tx.encode()))
        v,r,s=ecsign(before_hash,normalize_key(privtKey))
        signature = binascii.hexlify(int_to_big_endian(r) + int_to_big_endian(s) +
                                     bytes(chr(v).encode())).decode()
        return signature




    def broadcast(self, unsigned_tx,signature):
        '''
        将未签名的交易和签名组装在一起广播出去
        '''
        signature=binascii.unhexlify(signature.encode())
        unsigned_tx=binascii.unhexlify(unsigned_tx.encode())
        r = signature[0:32]
        s = signature[32:64]
        v = bytes(chr(signature[64]).encode())

        unsigned_items = rlp.decode(unsigned_tx)
        unsigned_items.extend([v, r, s])
        signed_items = unsigned_items

        signed_tx_data = rlp.encode(signed_items)
        print(binascii.hexlify(signed_tx_data))
        tx_id = self.web3.eth.sendRawTransaction(signed_tx_data)
        return "0x"+binascii.hexlify(tx_id).decode()

    def sendTransaction(self,addressFrom, addressTo, value,startgas,data,privtKey):
        tx = Transaction(
            nonce=self.web3.eth.getTransactionCount(addressFrom),
            gasprice=self.web3.eth.gasPrice,
            startgas=startgas,
            to=addressTo,
            value=value,
            data=data
        )

        tx.sign(privtKey)
        raw_tx = self.web3.toHex(rlp.encode(tx))
        tx_id = self.web3.eth.sendRawTransaction(raw_tx)

        return tx_id.hex()


    def deploy_contract(self,contract_file_name, contract_name,addressFrom, privtKey,contract_args=None,import_remappings=[]):
        '''
        部署合约
        '''
        # compiled_sol = compile_source(solidity_code)
        compiled_sol = compile_files([contract_file_name],import_remappings=import_remappings)
        contract_interface = compiled_sol.get("{}:{}".format(contract_file_name, contract_name))
        bytecode = contract_interface['bin']
        if contract_args:
            bytecode += hander_contract_args(contract_args)
        data = binascii.unhexlify(bytecode.encode())
        gas_limit = self.web3.eth.estimateGas({"from": addressFrom, "value": "0x0", "data": bytecode})
        tx_id = self.sendTransaction(
                                addressFrom=addressFrom,
                                addressTo="",
                                value=0,
                                startgas=gas_limit,
                                data=data,
                                privtKey=privtKey
        )

        # tx_receipt = w3.eth.waitForTransactionReceipt(reszult)
        # contract_address = tx_receipt['contractAddress']

        return tx_id


    def get_contract_instance(self, contract_address, abi):
        '''
        获取合约实例
        '''
        contract = self.web3.eth.contract(address=checksum_encode(contract_address), abi=abi)
        return contract

    def invoke_contract(self, invoker, contract, method, args,gasPrice=None):
        '''
        调用合约里实现的方法
        '''
        tx_dict = contract.functions[method](*args).buildTransaction({
            'gasPrice': self.web3.eth.gasPrice if not gasPrice else gasPrice,
            'nonce': self.web3.eth.getTransactionCount(invoker),
        })
        tx = Transaction(
            nonce=tx_dict.get("nonce"),
            gasprice=tx_dict.get("gasPrice"),
            startgas=tx_dict.get("gas"),
            to=tx_dict.get("to"),
            value=tx_dict.get("value"),
            data=binascii.unhexlify(tx_dict.get("data")[2:]))

        UnsignedTransaction = Transaction.exclude(['v', 'r', 's'])
        unsigned_tx = rlp.encode(tx, UnsignedTransaction)

        return binascii.hexlify(unsigned_tx).decode()

    def sign_args(self, typeList, valueList, privtKey):
        '''

        :param typeList: ['bytes32', 'bytes32', 'uint256', 'uint256']
        :param valueList: ["0x3ae88fe370c39384fc16da2c9e768cf5d2495b48", "0x9da26fc2e1d6ad9fdd46138906b0104ae68a65d8", 1, 1]
        :param privtKey: "095e53c9c20e23fd01eaad953c01da9e9d3ed9bebcfed8e5b2c2fce94037d963"
        :return:
        '''
        data_hash = Web3.soliditySha3(typeList, valueList)
        v, r, s = ecsign(data_hash, normalize_key(privtKey))
        signature = binascii.hexlify(int_to_big_endian(r) + int_to_big_endian(s)
                                     + bytes(chr(v - 27).encode()))
        return signature

    def get_balance_of_eth(self,address):
        return self.web3.eth.getBalance(address)/(10**18)

    def get_balance_of_erc20(self,contract,address):
        return contract.functions.balanceOf(checksum_encode(address)).call()/(10**8)

    def get_transaction_by_hash(self,txId):
        res=self.web3.eth.getTransaction(txId)
        return res

    def get_transaction_receipt_by_hash(self,txId):
        res=self.web3.eth.getTransactionReceipt(txId)
        return res


    def get_gas_price(self):
        gas_price=self.web3.eth.gasPrice
        return gas_price

if __name__ == "__main__":
    CONTRACT_ADDRESS = "0x65096f2b7a8dc1592479f1911cd2b98dae4d2218"
    CONTRACT_ABI = [
        {
            "anonymous": False,
            "inputs": [
                {
                    "indexed": True,
                    "name": "from",
                    "type": "address"
                },
                {
                    "indexed": True,
                    "name": "to",
                    "type": "address"
                },
                {
                    "indexed": False,
                    "name": "value",
                    "type": "uint256"
                }
            ],
            "name": "Transfer",
            "type": "event"
        },
        {
            "constant": False,
            "inputs": [
                {
                    "name": "_spender",
                    "type": "address"
                },
                {
                    "name": "_value",
                    "type": "uint256"
                }
            ],
            "name": "approve",
            "outputs": [
                {
                    "name": "",
                    "type": "bool"
                }
            ],
            "payable": False,
            "stateMutability": "nonpayable",
            "type": "function"
        },
        {
            "constant": False,
            "inputs": [
                {
                    "name": "_value",
                    "type": "uint256"
                }
            ],
            "name": "burn",
            "outputs": [
                {
                    "name": "",
                    "type": "bool"
                }
            ],
            "payable": False,
            "stateMutability": "nonpayable",
            "type": "function"
        },
        {
            "constant": False,
            "inputs": [
                {
                    "name": "_from",
                    "type": "address"
                },
                {
                    "name": "_value",
                    "type": "uint256"
                }
            ],
            "name": "burnFrom",
            "outputs": [
                {
                    "name": "",
                    "type": "bool"
                }
            ],
            "payable": False,
            "stateMutability": "nonpayable",
            "type": "function"
        },
        {
            "constant": False,
            "inputs": [
                {
                    "name": "newAdmin",
                    "type": "address"
                }
            ],
            "name": "changeAdmin",
            "outputs": [],
            "payable": False,
            "stateMutability": "nonpayable",
            "type": "function"
        },
        {
            "constant": False,
            "inputs": [
                {
                    "name": "newOwner",
                    "type": "address"
                }
            ],
            "name": "changeAll",
            "outputs": [],
            "payable": False,
            "stateMutability": "nonpayable",
            "type": "function"
        },
        {
            "constant": False,
            "inputs": [
                {
                    "name": "_spender",
                    "type": "address"
                },
                {
                    "name": "_subtractedValue",
                    "type": "uint256"
                }
            ],
            "name": "decreaseApproval",
            "outputs": [
                {
                    "name": "success",
                    "type": "bool"
                }
            ],
            "payable": False,
            "stateMutability": "nonpayable",
            "type": "function"
        },
        {
            "constant": False,
            "inputs": [
                {
                    "name": "token",
                    "type": "address"
                },
                {
                    "name": "amount",
                    "type": "uint256"
                }
            ],
            "name": "emergencyERC20Drain",
            "outputs": [],
            "payable": False,
            "stateMutability": "nonpayable",
            "type": "function"
        },
        {
            "constant": False,
            "inputs": [
                {
                    "name": "_spender",
                    "type": "address"
                },
                {
                    "name": "_addedValue",
                    "type": "uint256"
                }
            ],
            "name": "increaseApproval",
            "outputs": [
                {
                    "name": "success",
                    "type": "bool"
                }
            ],
            "payable": False,
            "stateMutability": "nonpayable",
            "type": "function"
        },
        {
            "constant": False,
            "inputs": [
                {
                    "name": "newPausedPublic",
                    "type": "bool"
                },
                {
                    "name": "newPausedOwnerAdmin",
                    "type": "bool"
                }
            ],
            "name": "pause",
            "outputs": [],
            "payable": False,
            "stateMutability": "nonpayable",
            "type": "function"
        },
        {
            "anonymous": False,
            "inputs": [
                {
                    "indexed": True,
                    "name": "_burner",
                    "type": "address"
                },
                {
                    "indexed": False,
                    "name": "_value",
                    "type": "uint256"
                }
            ],
            "name": "Burn",
            "type": "event"
        },
        {
            "anonymous": False,
            "inputs": [
                {
                    "indexed": True,
                    "name": "previousOwner",
                    "type": "address"
                },
                {
                    "indexed": True,
                    "name": "newOwner",
                    "type": "address"
                }
            ],
            "name": "OwnershipTransferred",
            "type": "event"
        },
        {
            "anonymous": False,
            "inputs": [
                {
                    "indexed": False,
                    "name": "newState",
                    "type": "bool"
                }
            ],
            "name": "PauseOwnerAdmin",
            "type": "event"
        },
        {
            "anonymous": False,
            "inputs": [
                {
                    "indexed": False,
                    "name": "newState",
                    "type": "bool"
                }
            ],
            "name": "PausePublic",
            "type": "event"
        },
        {
            "constant": False,
            "inputs": [
                {
                    "name": "_to",
                    "type": "address"
                },
                {
                    "name": "_value",
                    "type": "uint256"
                }
            ],
            "name": "transfer",
            "outputs": [
                {
                    "name": "",
                    "type": "bool"
                }
            ],
            "payable": False,
            "stateMutability": "nonpayable",
            "type": "function"
        },
        {
            "anonymous": False,
            "inputs": [
                {
                    "indexed": True,
                    "name": "previousAdmin",
                    "type": "address"
                },
                {
                    "indexed": True,
                    "name": "newAdmin",
                    "type": "address"
                }
            ],
            "name": "AdminTransferred",
            "type": "event"
        },
        {
            "anonymous": False,
            "inputs": [
                {
                    "indexed": True,
                    "name": "owner",
                    "type": "address"
                },
                {
                    "indexed": True,
                    "name": "spender",
                    "type": "address"
                },
                {
                    "indexed": False,
                    "name": "value",
                    "type": "uint256"
                }
            ],
            "name": "Approval",
            "type": "event"
        },
        {
            "constant": False,
            "inputs": [
                {
                    "name": "_from",
                    "type": "address"
                },
                {
                    "name": "_to",
                    "type": "address"
                },
                {
                    "name": "_value",
                    "type": "uint256"
                }
            ],
            "name": "transferFrom",
            "outputs": [
                {
                    "name": "",
                    "type": "bool"
                }
            ],
            "payable": False,
            "stateMutability": "nonpayable",
            "type": "function"
        },
        {
            "constant": False,
            "inputs": [
                {
                    "name": "newOwner",
                    "type": "address"
                }
            ],
            "name": "transferOwnership",
            "outputs": [],
            "payable": False,
            "stateMutability": "nonpayable",
            "type": "function"
        },
        {
            "inputs": [],
            "payable": False,
            "stateMutability": "nonpayable",
            "type": "constructor"
        },
        {
            "constant": True,
            "inputs": [],
            "name": "admin",
            "outputs": [
                {
                    "name": "",
                    "type": "address"
                }
            ],
            "payable": False,
            "stateMutability": "view",
            "type": "function"
        },
        {
            "constant": True,
            "inputs": [
                {
                    "name": "_owner",
                    "type": "address"
                },
                {
                    "name": "_spender",
                    "type": "address"
                }
            ],
            "name": "allowance",
            "outputs": [
                {
                    "name": "",
                    "type": "uint256"
                }
            ],
            "payable": False,
            "stateMutability": "view",
            "type": "function"
        },
        {
            "constant": True,
            "inputs": [
                {
                    "name": "_owner",
                    "type": "address"
                }
            ],
            "name": "balanceOf",
            "outputs": [
                {
                    "name": "balance",
                    "type": "uint256"
                }
            ],
            "payable": False,
            "stateMutability": "view",
            "type": "function"
        },
        {
            "constant": True,
            "inputs": [],
            "name": "decimals",
            "outputs": [
                {
                    "name": "",
                    "type": "uint8"
                }
            ],
            "payable": False,
            "stateMutability": "view",
            "type": "function"
        },
        {
            "constant": True,
            "inputs": [],
            "name": "name",
            "outputs": [
                {
                    "name": "",
                    "type": "string"
                }
            ],
            "payable": False,
            "stateMutability": "view",
            "type": "function"
        },
        {
            "constant": True,
            "inputs": [],
            "name": "owner",
            "outputs": [
                {
                    "name": "",
                    "type": "address"
                }
            ],
            "payable": False,
            "stateMutability": "view",
            "type": "function"
        },
        {
            "constant": True,
            "inputs": [],
            "name": "pausedOwnerAdmin",
            "outputs": [
                {
                    "name": "",
                    "type": "bool"
                }
            ],
            "payable": False,
            "stateMutability": "view",
            "type": "function"
        },
        {
            "constant": True,
            "inputs": [],
            "name": "pausedPublic",
            "outputs": [
                {
                    "name": "",
                    "type": "bool"
                }
            ],
            "payable": False,
            "stateMutability": "view",
            "type": "function"
        },
        {
            "constant": True,
            "inputs": [],
            "name": "symbol",
            "outputs": [
                {
                    "name": "",
                    "type": "string"
                }
            ],
            "payable": False,
            "stateMutability": "view",
            "type": "function"
        },
        {
            "constant": True,
            "inputs": [],
            "name": "totalSupply",
            "outputs": [
                {
                    "name": "",
                    "type": "uint256"
                }
            ],
            "payable": False,
            "stateMutability": "view",
            "type": "function"
        }
    ]
    myclient = Client("https://ropsten.infura.io/pZc5ZTRYM8wYfRPtoQal")
    contract = myclient.get_contract_instance(CONTRACT_ADDRESS, CONTRACT_ABI)


    a=myclient.sign_args(typeList=['bytes32', 'bytes32', 'uint256', 'uint256',"uint256"],
                         valueList=["0x3ae88fe370c39384fc16da2c9e768cf5d2495b48","0x537C8f3d3E18dF5517a58B3fB9D9143697996802",20,20,0],
                         privtKey="095e53c9c20e23fd01eaad953c01da9e9d3ed9bebcfed8e5b2c2fce94037d963")


    # TXID = myclient.deploy_contract("Rating.sol","RatingToken",
    #                "0x3aE88fe370c39384FC16dA2C9e768Cf5d2495b48","095e53c9c20e23fd01eaad953c01da9e9d3ed9bebcfed8e5b2c2fce94037d963",[])
    # print(TXID)
    pass
