#  Copyright 2018 Ocean Protocol Foundation
#  SPDX-License-Identifier: Apache-2.0
import json
import logging
import os
from collections import namedtuple

import eth_account
import eth_keys
import eth_utils
from eth_keys import KeyAPI
from eth_utils import big_endian_to_int
from ocean_lib.web3_internal.web3_provider import Web3Provider
from web3 import Web3

Signature = namedtuple("Signature", ("v", "r", "s"))

logger = logging.getLogger(__name__)


def generate_multi_value_hash(types, values):
    """
    Return the hash of the given list of values.
    This is equivalent to packing and hashing values in a solidity smart contract
    hence the use of `soliditySha3`.

    :param types: list of solidity types expressed as strings
    :param values: list of values matching the `types` list
    :return: bytes
    """
    assert len(types) == len(values)
    return Web3.soliditySha3(types, values)


def prepare_prefixed_hash(msg_hash):
    """

    :param msg_hash:
    :return:
    """
    return generate_multi_value_hash(
        ["string", "bytes32"], ["\x19Ethereum Signed Message:\n32", msg_hash]
    )


def add_ethereum_prefix_and_hash_msg(text):
    """
    This method of adding the ethereum prefix seems to be used in web3.personal.sign/ecRecover.

    :param text: str any str to be signed / used in recovering address from a signature
    :return: hash of prefixed text according to the recommended ethereum prefix
    """
    prefixed_msg = f"\x19Ethereum Signed Message:\n{len(text)}{text}"
    return Web3.sha3(text=prefixed_msg)


def get_public_key_from_address(web3, account):
    """

    :param web3:
    :param account:
    :return:
    """
    _hash = web3.sha3(text="verify signature.")
    signature = web3.personal.sign(_hash, account.address, account.password)
    signature = split_signature(web3, web3.toBytes(hexstr=signature))
    signature_vrs = Signature(
        signature.v % 27, big_endian_to_int(signature.r), big_endian_to_int(signature.s)
    )
    prefixed_hash = prepare_prefixed_hash(_hash)
    pub_key = KeyAPI.PublicKey.recover_from_msg_hash(
        prefixed_hash, KeyAPI.Signature(vrs=signature_vrs)
    )
    assert (
        pub_key.to_checksum_address() == account.address
    ), "recovered address does not match signing address."
    return pub_key


def to_32byte_hex(web3, val):
    """

    :param web3:
    :param val:
    :return:
    """
    return web3.toBytes(val).rjust(32, b"\0")


def split_signature(web3, signature):
    """

    :param web3:
    :param signature: signed message hash, hex str
    :return:
    """
    assert len(signature) == 65, (
        f"invalid signature, " f"expecting bytes of length 65, got {len(signature)}"
    )
    v = web3.toInt(signature[-1])
    r = to_32byte_hex(web3, int.from_bytes(signature[:32], "big"))
    s = to_32byte_hex(web3, int.from_bytes(signature[32:64], "big"))
    if v != 27 and v != 28:
        v = 27 + v % 2

    return Signature(v, r, s)


def get_wallet(index):
    name = "PARITY_ADDRESS" if not index else f"PARITY_ADDRESS{index}"
    pswrd_name = "PARITY_PASSWORD" if not index else f"PARITY_PASSWORD{index}"
    key_name = "PARITY_KEY" if not index else f"PARITY_KEY{index}"
    encrypted_key_name = (
        "PARITY_ENCRYPTED_KEY" if not index else f"PARITY_ENCRYPTED_KEY{index}"
    )
    keyfile_name = "PARITY_KEYFILE" if not index else f"PARITY_KEYFILE{index}"

    address = os.getenv(name)
    if not address:
        return None

    pswrd = os.getenv(pswrd_name)
    key = os.getenv(key_name)
    encr_key = os.getenv(encrypted_key_name)
    key_file = os.getenv(keyfile_name)
    if key_file and not encr_key:
        with open(key_file) as _file:
            encr_key = json.loads(_file.read())

    from ocean_lib.web3_internal.wallet import Wallet

    return Wallet(
        Web3Provider.get_web3(),
        private_key=key,
        encrypted_key=encr_key,
        address=Web3.toChecksumAddress(address),
        password=pswrd,
    )


def privateKeyToAddress(private_key: str) -> str:
    return eth_account.Account().privateKeyToAccount(private_key).address


def privateKeyToPublicKey(private_key: str):
    private_key_bytes = eth_utils.decode_hex(private_key)
    private_key_object = eth_keys.keys.PrivateKey(private_key_bytes)
    return private_key_object.public_key

[((16, 12, 16, 52), 'collections.namedtuple', 'namedtuple', ({(16, 23, 16, 34): '"""Signature"""', (16, 36, 16, 51): "('v', 'r', 's')"}, {}), "('Signature', ('v', 'r', 's'))", False, 'from collections import namedtuple\n'), ((18, 9, 18, 36), 'logging.getLogger', 'logging.getLogger', ({(18, 27, 18, 35): '__name__'}, {}), '(__name__)', False, 'import logging\n'), ((32, 11, 32, 43), 'web3.Web3.soliditySha3', 'Web3.soliditySha3', ({(32, 29, 32, 34): 'types', (32, 36, 32, 42): 'values'}, {}), '(types, values)', False, 'from web3 import Web3\n'), ((54, 11, 54, 39), 'web3.Web3.sha3', 'Web3.sha3', (), '', False, 'from web3 import Web3\n'), ((118, 14, 118, 29), 'os.getenv', 'os.getenv', ({(118, 24, 118, 28): 'name'}, {}), '(name)', False, 'import os\n'), ((122, 12, 122, 33), 'os.getenv', 'os.getenv', ({(122, 22, 122, 32): 'pswrd_name'}, {}), '(pswrd_name)', False, 'import os\n'), ((123, 10, 123, 29), 'os.getenv', 'os.getenv', ({(123, 20, 123, 28): 'key_name'}, {}), '(key_name)', False, 'import os\n'), ((124, 15, 124, 44), 'os.getenv', 'os.getenv', ({(124, 25, 124, 43): 'encrypted_key_name'}, {}), '(encrypted_key_name)', False, 'import os\n'), ((125, 15, 125, 38), 'os.getenv', 'os.getenv', ({(125, 25, 125, 37): 'keyfile_name'}, {}), '(keyfile_name)', False, 'import os\n'), ((146, 24, 146, 57), 'eth_utils.decode_hex', 'eth_utils.decode_hex', ({(146, 45, 146, 56): 'private_key'}, {}), '(private_key)', False, 'import eth_utils\n'), ((147, 25, 147, 68), 'eth_keys.keys.PrivateKey', 'eth_keys.keys.PrivateKey', ({(147, 50, 147, 67): 'private_key_bytes'}, {}), '(private_key_bytes)', False, 'import eth_keys\n'), ((68, 26, 68, 56), 'eth_utils.big_endian_to_int', 'big_endian_to_int', ({(68, 44, 68, 55): 'signature.r'}, {}), '(signature.r)', False, 'from eth_utils import big_endian_to_int\n'), ((68, 58, 68, 88), 'eth_utils.big_endian_to_int', 'big_endian_to_int', ({(68, 76, 68, 87): 'signature.s'}, {}), '(signature.s)', False, 'from eth_utils import big_endian_to_int\n'), ((72, 23, 72, 58), 'eth_keys.KeyAPI.Signature', 'KeyAPI.Signature', (), '', False, 'from eth_keys import KeyAPI\n'), ((133, 8, 133, 31), 'ocean_lib.web3_internal.web3_provider.Web3Provider.get_web3', 'Web3Provider.get_web3', ({}, {}), '()', False, 'from ocean_lib.web3_internal.web3_provider import Web3Provider\n'), ((136, 16, 136, 47), 'web3.Web3.toChecksumAddress', 'Web3.toChecksumAddress', ({(136, 39, 136, 46): 'address'}, {}), '(address)', False, 'from web3 import Web3\n'), ((142, 11, 142, 32), 'eth_account.Account', 'eth_account.Account', ({}, {}), '()', False, 'import eth_account\n')]