import os

import pytest
from web3 import HTTPProvider, Web3

from raiden.constants import Environment
from raiden.network.blockchain_service import BlockChainService
from raiden.network.discovery import ContractDiscovery
from raiden.network.rpc.client import JSONRPCClient
from raiden.tests.utils.geth import GethNodeDescription, geth_run_private_blockchain
from raiden.tests.utils.network import jsonrpc_services
from raiden.tests.utils.tests import cleanup_tasks
from raiden.utils import privatekey_to_address
from raiden_contracts.contract_manager import ContractManager, contracts_precompiled_path

# pylint: disable=redefined-outer-name,too-many-arguments,unused-argument,too-many-locals

_GETH_DATADIR = os.environ.get('RAIDEN_TESTS_GETH_DATADIR', False)


@pytest.fixture
def endpoint_discovery_services(blockchain_services, endpoint_registry_address):
    return [
        ContractDiscovery(chain.node_address, chain.discovery(endpoint_registry_address))
        for chain in blockchain_services.blockchain_services
    ]


@pytest.fixture
def web3(
        blockchain_p2p_ports,
        blockchain_private_keys,
        blockchain_rpc_ports,
        blockchain_type,
        deploy_key,
        private_keys,
        random_marker,
        request,
        tmpdir,
        chain_id,
):
    """ Starts a private chain with accounts funded. """
    # include the deploy key in the list of funded accounts
    keys_to_fund = set(private_keys)
    keys_to_fund.add(deploy_key)
    keys_to_fund = sorted(keys_to_fund)

    if blockchain_type == 'geth':
        host = '0.0.0.0'
        rpc_port = blockchain_rpc_ports[0]
        endpoint = f'http://{host}:{rpc_port}'
        web3 = Web3(HTTPProvider(endpoint))

        assert len(blockchain_private_keys) == len(blockchain_rpc_ports)
        assert len(blockchain_private_keys) == len(blockchain_p2p_ports)

        geth_nodes = [
            GethNodeDescription(
                key,
                rpc,
                p2p,
                miner=(pos == 0),
            )
            for pos, (key, rpc, p2p) in enumerate(zip(
                blockchain_private_keys,
                blockchain_rpc_ports,
                blockchain_p2p_ports,
            ))
        ]

        accounts_to_fund = [
            privatekey_to_address(key)
            for key in keys_to_fund
        ]

        if _GETH_DATADIR:
            base_datadir = _GETH_DATADIR
            os.makedirs(base_datadir, exist_ok=True)
        else:
            base_datadir = str(tmpdir)

        geth_processes = geth_run_private_blockchain(
            web3=web3,
            accounts_to_fund=accounts_to_fund,
            geth_nodes=geth_nodes,
            base_datadir=base_datadir,
            chain_id=chain_id,
            verbosity=request.config.option.verbose,
            random_marker=random_marker,
        )

        yield web3

        for process in geth_processes:
            process.terminate()

        cleanup_tasks()

    else:
        raise ValueError(f'unknown blockchain_type {blockchain_type}')


@pytest.fixture
def deploy_client(blockchain_rpc_ports, deploy_key, web3):
    return JSONRPCClient(web3, deploy_key)


@pytest.fixture
def testing_contracts_version():
    return None


@pytest.fixture
def contract_manager(environment_type):
    version = None
    if environment_type == Environment.DEVELOPMENT:
        version = 'pre_limits'

    return ContractManager(contracts_precompiled_path(version))


@pytest.fixture
def deploy_service(deploy_key, deploy_client, contract_manager):
    return BlockChainService(
        jsonrpc_client=deploy_client,
        contract_manager=contract_manager,
    )


@pytest.fixture
def blockchain_services(
        deploy_service,
        private_keys,
        secret_registry_address,
        token_network_registry_address,
        web3,
        contract_manager,
):
    return jsonrpc_services(
        deploy_service,
        private_keys,
        secret_registry_address,
        token_network_registry_address,
        web3=web3,
        contract_manager=contract_manager,
    )
