import importlib
import web3
import grpc

from rfc3986 import urlparse
from eth_account.messages import defunct_hash_message
from snet_cli.utils import RESOURCES_PATH, add_to_path, bytes32_to_str
from sdk.mpe_contract import MPEContract
from sdk.account import Account
from sdk.payment_channel import PaymentChannel


class ServiceClient:
    def __init__(self, config, metadata, options):
        self._config = config
        eth_rpc_endpoint = self._config.get(
            "eth_rpc_endpoint", "https://mainnet.infura.io")
        provider = web3.HTTPProvider(eth_rpc_endpoint)
        self.web3 = web3.Web3(provider)
        self.mpe_contract = MPEContract(self.web3)
        self.account = Account(self.web3, self._config, self.mpe_contract)

        self.options = options
        self.metadata = metadata
        self.expiry_threshold = self.metadata["payment_expiration_threshold"]
        self._base_grpc_channel = self._get_grpc_channel()
        self.payment_channel_state_service_client = self._generate_payment_channel_state_service_client()
        self.payment_channels = []
        self.last_read_block = 0

    def _get_grpc_channel(self):
        endpoint = self.options.get("endpoint", None)
        if endpoint is None:
            endpoint = self.metadata["endpoint"]
        endpoint_object = urlparse(endpoint)
        if endpoint_object.port is not None:
            channel_endpoint = endpoint_object.hostname + \
                ":" + str(endpoint_object.port)
        else:
            channel_endpoint = endpoint_object.hostname

        print("Opening grpc to " + channel_endpoint)
        if endpoint_object.scheme == "http":
            return grpc.insecure_channel(channel_endpoint)
        elif endpoint_object.scheme == "https":
            return grpc.secure_channel(channel_endpoint, grpc.ssl_channel_credentials())
        else:
            raise ValueError('Unsupported scheme in service metadata ("{}")'.format(
                endpoint_object.scheme))

    def get_service_call_metadata(self, channel_id):
        channel = PaymentChannel(channel_id, self.web3, self.account,
                                 self, self.mpe_contract)
        print("Got channel " + str(channel.channel_id) + " " +
              str(channel.account) + " " + str(channel.state))
        channel.sync_state()
        print("Sync channel " + str(channel.channel_id) + " " +
              str(channel.account) + " " + str(channel.state))
        amount = channel.state["last_signed_amount"] + \
            int(self.metadata["pricing"]["price_in_cogs"])
        message = web3.Web3.soliditySha3(
            ["address", "uint256", "uint256", "uint256"],
            [self.mpe_contract.contract.address,
                channel.channel_id, channel.state["nonce"], amount]
        )
        signature = bytes(self.web3.eth.account.signHash(
            defunct_hash_message(message), self.account.signer_private_key).signature)
        signature = str(signature.hex())
        metadata = {
            "snet-payment-channel-id": str(channel.channel_id),
            "snet-payment-channel-nonce": str(channel.state["nonce"]),
            "snet-payment-channel-amount": str(amount),
            "snet-payment-channel-signature-bin": signature
        }
        return metadata

    def update_channel_states(self):
        for channel in self.payment_channels:
            channel.sync_state()
        return self.payment_channels

    def default_channel_expiration(self):
        current_block_number = self.web3.eth.getBlock("latest").number
        return current_block_number + self.expiry_threshold

    def _generate_payment_channel_state_service_client(self):
        grpc_channel = self._base_grpc_channel
        with add_to_path(str(RESOURCES_PATH.joinpath("proto"))):
            state_service_pb2_grpc = importlib.import_module(
                "state_service_pb2_grpc")
        return state_service_pb2_grpc.PaymentChannelStateServiceStub(grpc_channel)
