"""
Base 58 conversion utilities
****************************
"""

#
# base58.py
# Original source: git://github.com/joric/brutus.git
# which was forked from git://github.com/samrushing/caesure.git
#
# Distributed under the MIT/X11 software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
#

from binascii import hexlify, unhexlify
from typing import List

from _common import hash256


b58_digits: str = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'


def encode(b: bytes) -> str:
    """
    Encode bytes to a base58-encoded string

    :param b: Bytes to encode
    :return: Base58 encoded string of ``b``
    """

    # Convert big-endian bytes to integer
    n: int = int('0x0' + hexlify(b).decode('utf8'), 16)

    # Divide that integer into base58
    temp: List[str] = []
    while n > 0:
        n, r = divmod(n, 58)
        temp.append(b58_digits[r])
    res: str = ''.join(temp[::-1])

    # Encode leading zeros as base58 zeros
    czero: int = 0
    pad: int = 0
    for c in b:
        if c == czero:
            pad += 1
        else:
            break
    return b58_digits[0] * pad + res

def decode(s: str) -> bytes:
    """
    Decode a base58-encoding string, returning bytes

    :param s: Base58 string to decode
    :return: Bytes encoded by ``s``
    """
    if not s:
        return b''

    # Convert the string to an integer
    n: int = 0
    for c in s:
        n *= 58
        if c not in b58_digits:
            raise Exception('Character %r is not a valid base58 character' % c)
        digit = b58_digits.index(c)
        n += digit

    # Convert the integer to bytes
    h: str = '%x' % n
    if len(h) % 2:
        h = '0' + h
    res = unhexlify(h.encode('utf8'))

    # Add padding back.
    pad = 0
    for c in s[:-1]:
        if c == b58_digits[0]:
            pad += 1
        else:
            break
    return b'\x00' * pad + res

def decode_check(s: str) -> bytes:
    """
    Decode a Base58Check encoded string, returning bytes

    :param s: Base58 string to decode
    :return: Bytes encoded by ``s``
    """
    data = decode(s)
    payload = data[:-4]
    checksum = data[-4:]
    calc_checksum = hash256(payload)
    if checksum != calc_checksum[:4]:
        raise ValueError("Invalid checksum")
    return payload

def encode_check(b: bytes) -> str:
    checksum = hash256(b)[0:4]
    data = b + checksum
    return encode(data)

def get_xpub_fingerprint(s: str) -> bytes:
    """
    Get the parent fingerprint from an extended public key

    :param s: The extended pubkey
    :return: The parent fingerprint bytes
    """
    data = decode(s)
    fingerprint = data[5:9]
    return fingerprint

def get_xpub_fingerprint_hex(xpub: str) -> str:
    """
    Get the parent fingerprint as a hex string from an extended public key

    :param xpub: The extended pubkey
    :return: The parent fingerprint as a hex string
    """
    data = decode(xpub)
    fingerprint = data[5:9]
    return hexlify(fingerprint).decode()

def to_address(b: bytes, version: bytes) -> str:
    """
    Base58 Check Encode the data with the version number.
    Used to encode legacy style addresses.

    :param b: The data to encode
    :param version: The version number to encode with
    :return: The Base58 Check Encoded string
    """
    data = version + b
    checksum = hash256(data)[0:4]
    data += checksum
    return encode(data)

def xpub_to_pub_hex(xpub: str) -> str:
    """
    Get the public key as a string from the extended public key.

    :param xpub: The extended pubkey
    :return: The pubkey hex string
    """
    data = decode(xpub)
    pubkey = data[-37:-4]
    return hexlify(pubkey).decode()


def xpub_to_xonly_pub_hex(xpub: str) -> str:
    """
    Get the public key as a string from the extended public key.

    :param xpub: The extended pubkey
    :return: The pubkey hex string
    """
    data = decode(xpub)
    pubkey = data[-36:-4]
    return hexlify(pubkey).decode()


def xpub_main_2_test(xpub: str) -> str:
    """
    Convert an extended pubkey from mainnet version to testnet version.

    :param xpub: The extended pubkey
    :return: The extended pubkey re-encoded using testnet version bytes
    """
    data = decode(xpub)
    test_data = b'\x04\x35\x87\xCF' + data[4:-4]
    checksum = hash256(test_data)[0:4]
    return encode(test_data + checksum)
