from jmbase import hextobin
from jmclient.configure import validate_address, load_test_config
from jmclient import jm_single
import jmbitcoin as btc
from bitcointx.wallet import CCoinAddress
from bitcointx import ChainParams
import json
import pytest
import os

pytestmark = pytest.mark.usefixtures("setup_regtest_bitcoind")

testdir = os.path.dirname(os.path.realpath(__file__))

def address_valid_somewhere(addr):
    for x in ["bitcoin", "bitcoin/testnet", "bitcoin/regtest"]:
        btc.select_chain_params(x)
        if validate_address(addr)[0]:
            return True
    return False

def test_non_addresses(setup_addresses):
    #could flesh this out with other examples
    res, msg = validate_address(2)
    assert res == False, "Incorrectly accepted number"

def test_b58_invalid_addresses(setup_addresses):
    #none of these are valid as any kind of key or address
    with open(os.path.join(testdir,"base58_keys_invalid.json"), "r") as f:
        json_data = f.read()
    invalid_key_list = json.loads(json_data)
    for k in invalid_key_list:
        bad_key = k[0]
        res = address_valid_somewhere(bad_key)
        assert res == False, "Incorrectly validated address: " + bad_key

def test_b58_valid_addresses():
    with open(os.path.join(testdir,"base58_keys_valid.json"), "r") as f:
        json_data = f.read()
    valid_keys_list = json.loads(json_data)
    for a in valid_keys_list:
        addr, pubkey, prop_dict = a
        if not prop_dict["isPrivkey"]:
            if prop_dict["isTestnet"]:
                jm_single().config.set("BLOCKCHAIN", "network", "testnet")
                btc.select_chain_params("bitcoin/testnet")
            else:
                jm_single().config.set("BLOCKCHAIN", "network", "mainnet")
                btc.select_chain_params("bitcoin")
            #if using pytest -s ; sanity check to see what's actually being tested
            res, message = validate_address(addr)
            assert res == True, "Incorrectly failed to validate address: " + addr + " with message: " + message
    jm_single().config.set("BLOCKCHAIN", "network", "testnet")
    btc.select_chain_params("bitcoin/regtest")

def test_valid_bech32_addresses():
    valids = ["BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
    "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
    # TODO these are valid bech32 addresses but rejected by bitcointx
    # because they are not witness version 0; add others.
    #"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",
    #"BC1SW50QA3JX3S",
    #"bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj",
    "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy"]
    for va in valids:
        if va.lower()[:2] == "bc":
            jm_single().config.set("BLOCKCHAIN", "network", "mainnet")
            btc.select_chain_params("bitcoin")
        else:
            jm_single().config.set("BLOCKCHAIN", "network", "testnet")
            btc.select_chain_params("bitcoin/testnet")
        res, message = validate_address(va)
        assert res == True, "Incorrect failed to validate address: " + va + " with message: " + message
    jm_single().config.set("BLOCKCHAIN", "network", "testnet")
    btc.select_chain_params("bitcoin/regtest")

def test_invalid_bech32_addresses():
    invalids = [
    "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty",
    "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5",
    "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2",
    "bc1rw5uspcuh",
    "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90",
    "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
    "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7",
    "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du",
    "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
    "bc1gmk9yu"]
    for iva in invalids:
        res = address_valid_somewhere(iva)
        assert res == False, "Incorrectly validated address: " + iva

def test_valid_bip341_scriptpubkeys_addresses():
    with ChainParams("bitcoin"):
        with open(os.path.join(testdir,
            "bip341_wallet_test_vectors.json"), "r") as f:
            json_data = json.loads(f.read())
        for x in json_data["scriptPubKey"]:
            sPK = hextobin(x["expected"]["scriptPubKey"])
            addr = x["expected"]["bip350Address"]
            res, message = validate_address(addr)
            assert res, message
            print("address {} was valid bech32m".format(addr))
            # test this specific conversion because this is how
            # our human readable outputs work:
            assert str(CCoinAddress.from_scriptPubKey(
                btc.CScript(sPK))) == addr
            print("and it converts correctly from scriptPubKey: {}".format(
                btc.CScript(sPK)))

# These tests are almost, but not quite, unnecessary:
# we are testing the same failure cases, from BIP350, that
# are already tested in our underlying bitcoin package bitcointx;
# but this represents a sanity check that our top-layer validation
# check is functioning, whatever the backend. We focus strongly on
# detecting invalid encodings/address strings since that is where the
# actual danger of loss exists.
invalid_bech32_bech32m = [
  ["an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4", "overall max length exceeded"],
  ["qyrz8wqd2c9m", "No separator character"],
  ["1qyrz8wqd2c9m", "Empty HRP"],
  ["y1b0jsk6g", "Invalid data character"],
  ["lt1igcx5c0", "Invalid data character"],
  ["in1muywd", "Too short checksum"],
  ["mm1crxm3i", "Invalid character in checksum"],
  ["au1s5cgom", "Invalid character in checksum"],
  ["M1VUXWEZ", "checksum calculated with uppercase form of HRP"],
  ["16plkw9", "empty HRP"],
  ["1p2gdwpf", "empty HRP"],
  ["tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", "Invalid human-readable part"],
  ["bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", "Invalid checksum"],
  ["BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", "Invalid witness version"],
  ["bc1rw5uspcuh", "Invalid program length"],
  ["bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", "Invalid program length"],
  ["BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", "Invalid program length for witness version 0 (per BIP141)"],
  ["tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", "Mixed case"],
  ["bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", "zero padding of more than 4 bits"],
  ["tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", "Non-zero padding in 8-to-5 conversion"],
  ["bc1gmk9yu", "Empty data section"],
  ["tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut", "Invalid human-readable part"],
  ["bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd", "Invalid checksum (Bech32 instead of Bech32m)"],
  ["tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf", "Invalid checksum (Bech32 instead of Bech32m)"],
  ["BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL", "Invalid checksum (Bech32 instead of Bech32m)"],
  ["bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh", "Invalid checksum (Bech32m instead of Bech32)"],
  ["tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47", "Invalid checksum (Bech32m instead of Bech32)"],
  ["bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4", "Invalid character in checksum"],
  ["BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R", "Invalid witness version"],
  ["bc1pw5dgrnzv", "Invalid program length (1 byte)"],
  ["bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav", "Invalid program length (41 bytes)"],
  ["tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq", "Mixed case"],
  ["bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf", "zero padding of more than 4 bits"],
  ["tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j", "Non-zero padding in 8-to-5 conversion"],
  ["bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", "non-zero version, but bech32 (not bech32m)"],
  ["BC1SW50QA3JX3S", "non-zero version, but bech32 (not bech32m)"],
  ["bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "non-zero version, but bech32 (not bech32m)"]]
def test_invalid_bech32m():
    for case in invalid_bech32_bech32m:
        assert not address_valid_somewhere(case[0])

@pytest.fixture(scope="module")
def setup_addresses():
    load_test_config()
