# standard imports
import os
import logging
import json

# third-party imports
import pytest
from cic_registry.bancor import contract_ids
from cic_registry import bancor

# local imports
from cic_eth.eth import rpc

script_dir = os.path.dirname(os.path.realpath(__file__))
root_dir = os.path.dirname(script_dir)

logg = logging.getLogger(__file__)


class BancorContractLoader:

    bancor_path = os.path.join(root_dir, 'bancor')
    registry_contract = None

    @staticmethod
    def build_path():
        return BancorContractLoader.bancor_path
#        return os.path.join(BancorContractLoader.bancor_path, 'solidity', 'build', 'contracts')


    @staticmethod
    def contract(w3, bundle_id, registry_id=None):
        if registry_id == None:
            registry_id = bundle_id
        contract_id_hex = w3.toHex(text=registry_id)
        contract_address = BancorContractLoader.registry_contract.functions.addressOf(contract_id_hex).call()
        contract_build_file = os.path.join(
                BancorContractLoader.build_path(),
                '{}.json'.format(bundle_id),
                )
        f = open(os.path.join(contract_build_file))
        j = json.load(f)
        f.close()
        contract_abi = j['abi']
        logg.debug('creating contract interface {} ({}) at address {}'.format(registry_id, bundle_id, contract_address))
        contract = w3.eth.contract(abi=contract_abi, address=contract_address)
        return contract



# TODO: DRY
@pytest.fixture(scope='session')
def bancor_deploy(
        load_config,
        init_w3_conn,
        ):
    bancor_dir_default = os.path.join(root_dir, 'bancor')
    logg.debug('bancor deploy "{}"'.format(bancor_dir_default))
    BancorContractLoader.bancor_path = load_config.get('BANCOR_DIR', bancor_dir_default)
    bancor_build_dir = BancorContractLoader.build_path()

    # deploy registry
    registry_build_file = os.path.join(bancor_build_dir, 'ContractRegistry.json')
    f = open(os.path.join(registry_build_file))
    j = json.load(f)
    f.close()
    registry_constructor = init_w3_conn.eth.contract(abi=j['abi'], bytecode=j['bytecode'])
    tx = registry_constructor.constructor().transact()
    rcpt = init_w3_conn.eth.getTransactionReceipt(tx)
    registry_address = rcpt['contractAddress']
    registry_contract = init_w3_conn.eth.contract(abi=j['abi'], address=registry_address)
    BancorContractLoader.registry_contract = registry_contract

    # deply reserve token
    reservetoken_build_file = os.path.join(bancor_build_dir, 'EtherToken.json')
    f = open(os.path.join(reservetoken_build_file))
    j = json.load(f)
    f.close()
    reservetoken_constructor = init_w3_conn.eth.contract(abi=j['abi'], bytecode=j['bytecode'])
    tx = reservetoken_constructor.constructor('Reserve', 'RSV').transact()
    rcpt = init_w3_conn.eth.getTransactionReceipt(tx)
    reservetoken_address = rcpt['contractAddress']
    reservetoken_contract = init_w3_conn.eth.contract(abi=j['abi'], address=reservetoken_address)

    # register reserve token as bancor hub token
    key_hex = init_w3_conn.toHex(text='BNTToken')
    registry_contract.functions.registerAddress(key_hex, reservetoken_address).transact()

    # deposit balances for minting liquid tokens with reserve
    init_w3_conn.eth.sendTransaction({
        'from': init_w3_conn.eth.accounts[1],
        'to': reservetoken_address,
        'value': init_w3_conn.toWei('101', 'ether'),
        'nonce': 0,
        })
    init_w3_conn.eth.sendTransaction({
        'from': init_w3_conn.eth.accounts[2],
        'to': reservetoken_address,
        'value': init_w3_conn.toWei('101', 'ether'),
        'nonce': 0,
        })

    # deploy converter factory contract for creating liquid token exchanges
    build_file = os.path.join(bancor_build_dir, 'LiquidTokenConverterFactory.json')
    f = open(build_file)
    j = json.load(f)
    f.close()
    converterfactory_constructor = init_w3_conn.eth.contract(abi=j['abi'], bytecode=j['bytecode'])
    tx = converterfactory_constructor.constructor().transact()
    rcpt = init_w3_conn.eth.getTransactionReceipt(tx)
    converter_factory_address = rcpt['contractAddress']

    # deploy the remaining contracts managed by the registry
    for k in contract_ids.keys():
        build_file = os.path.join(bancor_build_dir, '{}.json'.format(k))
        f = open(build_file)
        j = json.load(f)
        f.close()
        contract_constructor = init_w3_conn.eth.contract(abi=j['abi'], bytecode=j['bytecode'])
        tx = None

        # include the registry address as constructor parameters for the contracts that require it
        if k in ['ConverterRegistry', 'ConverterRegistryData', 'BancorNetwork', 'ConversionPathFinder']:
            tx = contract_constructor.constructor(registry_address).transact()
        else:
            tx = contract_constructor.constructor().transact()
        rcpt = init_w3_conn.eth.getTransactionReceipt(tx)
        contract_address = rcpt['contractAddress']

        # register contract in registry
        key_hex = init_w3_conn.toHex(text=contract_ids[k])
        registry_contract.functions.registerAddress(key_hex, contract_address).transact()
        contract = init_w3_conn.eth.contract(abi=j['abi'], address=contract_address)

        # bancor formula needs to be initialized before use
        if k == 'BancorFormula':
            logg.debug('init bancor formula {}'.format(contract_address))
            contract.functions.init().transact()

        # converter factory needs liquid token converter factory to be able to issue our liquid tokens
        if k == 'ConverterFactory':
            logg.debug('register converter factory {}'.format(converter_factory_address))
            contract.functions.registerTypedConverterFactory(converter_factory_address).transact()
    
    logg.info('deployed registry at address {}'.format(registry_address))
    return registry_contract



def __create_converter(w3, converterregistry_contract, reserve_address, owner_address, token_name, token_symbol):
    converterregistry_contract.functions.newConverter(
            0, 
            token_name,
            token_symbol,
            18,
            100000,
            [reserve_address],
            [250000],
            ).transact({
                'from': owner_address,
                })


@pytest.fixture(scope='session')
def tokens_to_deploy(
        ):
    return [
        (1, 'Bert Token', 'BRT'), # account_index, token name, token symbol
        (2, 'Ernie Token', 'RNI'),
            ]


@pytest.fixture(scope='session')
def bancor_tokens(
    init_w3_conn,
    bancor_deploy,
    tokens_to_deploy,
    ):

    registry_contract = bancor_deploy

    reserve_contract = BancorContractLoader.contract(init_w3_conn, 'ERC20Token', 'BNTToken')
    reserve_address = reserve_contract.address

    network_id = init_w3_conn.toHex(text='BancorNetwork')
    network_address = registry_contract.functions.addressOf(network_id).call()

    converterregistry_contract = BancorContractLoader.contract(init_w3_conn, 'ConverterRegistry', 'BancorConverterRegistry')

    for p in tokens_to_deploy:
        __create_converter(init_w3_conn, converterregistry_contract, reserve_address, init_w3_conn.eth.accounts[p[0]], p[1], p[2])

    tokens = converterregistry_contract.functions.getAnchors().call()

    network_contract = BancorContractLoader.contract(init_w3_conn, 'BancorNetwork')

    mint_amount = init_w3_conn.toWei('100', 'ether')
    i = 0
    for token in tokens:
        i += 1
        owner = init_w3_conn.eth.accounts[i]
        logg.debug('owner {} is {}'.format(owner, token))
        reserve_contract.functions.approve(network_address, 0).transact({
            'from': owner
            })
        reserve_contract.functions.approve(network_address, mint_amount).transact({
            'from': owner
            })
        logg.debug('convert {} {} {} {}'.format(reserve_address, token, mint_amount, owner))
        network_contract.functions.convert([
            reserve_address,
            token,
            token,
            ],
            mint_amount,
            mint_amount,
            ).transact({
                'from': owner,
                })
    
    return tokens


@pytest.fixture(scope='session')
def bancor_load(
        load_config,
        init_w3_conn,
        bancor_deploy,
        bancor_tokens,
        ):
    registry_address = bancor_deploy.address
    bancor_dir_default = os.path.join(root_dir, 'bancor')
    bancor_dir = load_config.get('BANCOR_DIR', bancor_dir_default)
    bancor.load(init_w3_conn, registry_address, bancor_dir)