import pytest

from bsv.base58 import b58_encode, base58check_encode
from bsv.constants import Network, OpCode
from bsv.curve import curve
from bsv.utils import (
    address_to_public_key_hash,
    bits_to_bytes,
    bytes_to_bits,
    decode_address,
    decode_wif,
    deserialize_ecdsa_der,
    deserialize_ecdsa_recoverable,
    encode_int,
    encode_pushdata,
    get_pushdata_code,
    serialize_ecdsa_der,
    serialize_ecdsa_recoverable,
    stringify_ecdsa_recoverable,
    text_digest,
    unsigned_to_bytes,
    unsigned_to_varint,
    unstringify_ecdsa_recoverable,
    validate_address,
)


def test_unsigned_to_varint():
    assert unsigned_to_varint(0) == bytes.fromhex("00")
    assert unsigned_to_varint(0xFC) == bytes.fromhex("fc")

    assert unsigned_to_varint(0xFD) == bytes.fromhex("fdfd00")
    assert unsigned_to_varint(0xABCD) == bytes.fromhex("fdcdab")

    assert unsigned_to_varint(0x010000) == bytes.fromhex("fe00000100")
    assert unsigned_to_varint(0x12345678) == bytes.fromhex("fe78563412")

    assert unsigned_to_varint(0x0100000000) == bytes.fromhex("ff0000000001000000")
    assert unsigned_to_varint(0x1234567890ABCDEF) == bytes.fromhex("ffefcdab9078563412")

    with pytest.raises(OverflowError):
        unsigned_to_varint(-1)
    with pytest.raises(OverflowError):
        unsigned_to_varint(0x010000000000000000)


def test_unsigned_to_bytes():
    with pytest.raises(OverflowError):
        unsigned_to_bytes(-1)

    assert unsigned_to_bytes(0) == bytes.fromhex("00")
    assert unsigned_to_bytes(num=255, byteorder="big") == bytes.fromhex("ff")
    assert unsigned_to_bytes(num=256, byteorder="big") == bytes.fromhex("0100")

    assert unsigned_to_bytes(num=256, byteorder="little") == bytes.fromhex("0001")


def test_address():
    a1 = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
    pkh1 = bytes.fromhex("62e907b15cbf27d5425399ebf6f0fb50ebb88f18")
    assert decode_address(a1) == (pkh1, Network.MAINNET)

    a2 = "moEoqh2ZfYU8jN5EG6ERw6E3DmwnkuTdBC"
    pkh2 = bytes.fromhex("54b34b1ba228ba1d75dca5a40a114dc0f13a2687")
    assert decode_address(a2) == (pkh2, Network.TESTNET)

    a3 = "n34P4t4K6bJtc6qfGU2pqcRix8mUACdNyJ"
    pkh3 = bytes.fromhex("ec4c3733cff428e9a3c1434274b109fbe2a33b62")
    assert address_to_public_key_hash(a3) == pkh3

    address_invalid_prefix = base58check_encode(b"\xff" + bytes.fromhex("62e907b15cbf27d5425399ebf6f0fb50ebb88f18"))
    with pytest.raises(ValueError, match=r"invalid P2PKH address"):
        decode_address(address_invalid_prefix)

    address_invalid_checksum = b58_encode(b"\x00" + bytes.fromhex("62e907b15cbf27d5425399ebf6f0fb50ebb88f18") + b"\x00")
    with pytest.raises(ValueError, match=r"unmatched base58 checksum"):
        decode_address(address_invalid_checksum)

    assert validate_address("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa")
    assert validate_address("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", Network.MAINNET)
    assert not validate_address("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", Network.TESTNET)
    assert validate_address("moEoqh2ZfYU8jN5EG6ERw6E3DmwnkuTdBC", Network.TESTNET)
    assert not validate_address("moEoqh2ZfYU8jN5EG6ERw6E3DmwnkuTdB")
    assert not validate_address("")
    assert not validate_address(address_invalid_prefix)
    assert not validate_address(address_invalid_checksum)


def test_decode_wif():
    private_key_bytes = bytes.fromhex("f97c89aaacf0cd2e47ddbacc97dae1f88bec49106ac37716c451dcdd008a4b62")
    wif_compressed_main = "L5agPjZKceSTkhqZF2dmFptT5LFrbr6ZGPvP7u4A6dvhTrr71WZ9"
    wif_uncompressed_main = "5KiANv9EHEU4o9oLzZ6A7z4xJJ3uvfK2RLEubBtTz1fSwAbpJ2U"
    wif_compressed_test = "cVwfreZB3i8iv9JpdSStd9PWhZZGGJCFLS4rEKWfbkahibwhticA"
    wif_uncompressed_test = "93UnxexmsTYCmDJdctz4zacuwxQd5prDmH6rfpEyKkQViAVA3me"

    assert decode_wif(wif_compressed_main) == (private_key_bytes, True, Network.MAINNET)
    assert decode_wif(wif_uncompressed_main) == (private_key_bytes, False, Network.MAINNET)
    assert decode_wif(wif_compressed_test) == (private_key_bytes, True, Network.TESTNET)
    assert decode_wif(wif_uncompressed_test) == (private_key_bytes, False, Network.TESTNET)

    with pytest.raises(ValueError, match=r"unknown WIF prefix"):
        decode_wif(base58check_encode(b"\xff" + private_key_bytes))


def test_der_serialization():
    der1: str = (
        "3045022100fd5647a062d42cdde975ad4796cefd6b5613e731c08e0fb6907f757a60f44b02"
        "0220350fee392713423ebfcd8026ea29cc95917d823392f07cd6c80f46712650388e"
    )
    r1 = 114587593887127314608220924841831336233967095853165151956820984900193959037698
    s1 = 24000727837347392504013031837120627225728348681623127776947626422811445180558

    der2: str = (
        "304402207e2c6eb8c4b20e251a71c580373a2836e209c50726e5f8b0f4f59f8af00eee1a"
        "022019ae1690e2eb4455add6ca5b86695d65d3261d914bc1d7abb40b188c7f46c9a5"
    )
    r2 = 57069924365784604413146650701306419944030991562754207986153667089859857018394
    s2 = 11615408348402409164215774430388304177694127390766203039231142052414850779557

    der3: str = (
        "3044022023f093813911a658ac7cbaeb8ba7828b4067ea3582c78f8bd2c38b1f317489ba"
        "022000e1e43145a89f0d9d8524798b8ae2ca60ebf3947e35106d5e1ddf398985a033"
    )
    r3 = 16256011036517295435281672405882454685603286080662722236323812471789728336314
    s3 = 399115516115506318232804590771004057701078428754012727453057145885291814963

    assert serialize_ecdsa_der((r1, s1)).hex() == der1
    assert serialize_ecdsa_der((r1, curve.n - s1)).hex() == der1
    assert serialize_ecdsa_der((r2, s2)).hex() == der2
    assert serialize_ecdsa_der((r2, curve.n - s2)).hex() == der2
    assert serialize_ecdsa_der((r3, s3)).hex() == der3
    assert serialize_ecdsa_der((r3, curve.n - s3)).hex() == der3

    assert deserialize_ecdsa_der(bytes.fromhex(der1)) == (r1, s1)
    assert deserialize_ecdsa_der(bytes.fromhex(der2)) == (r2, s2)
    with pytest.raises(ValueError, match=r"invalid DER encoded"):
        deserialize_ecdsa_der(b"")


def test_recoverable_serialization():
    sig1 = "IGdzMq98lowek10e3JFXWj909xp0oLRj71aF7jpWRxaabwH+fBia/K2JpoGQlFFbAl/Q5jo2DYSzQw6pZWhmRtk="
    r1 = 46791760634954614230959036903197650877536710453529507613159894982805988775578
    s1 = 50210249429004071986853078788876176203428035162933045037212292756431067039449
    rec1 = 1
    serialized1, compressed1 = unstringify_ecdsa_recoverable(sig1)
    assert compressed1
    assert serialize_ecdsa_recoverable((r1, s1, rec1)) == serialized1
    assert deserialize_ecdsa_recoverable(serialized1) == (r1, s1, rec1)
    assert stringify_ecdsa_recoverable(serialized1, compressed1) == sig1

    sig2 = "G1CbjucJgMF/5lyS7LPZrLZPVU60RA6b7fq9b1zULG6uNq4PWQUD8HAvZMgKRPk/vkbDwN0ZsPwoVgKgV5rOSyI="
    r2 = 36459875458431662725541158294877706686723420026424146605771954142876183326382
    s2 = 24732431138926461036459634608851410023678722603615132417233328850542638549794
    rec2 = 0
    serialized2, compressed2 = unstringify_ecdsa_recoverable(sig2)
    assert not compressed2
    assert serialize_ecdsa_recoverable((r2, s2, rec2)) == serialized2
    assert deserialize_ecdsa_recoverable(serialized2) == (r2, s2, rec2)
    assert stringify_ecdsa_recoverable(serialized2, compressed2) == sig2


def test_text_digest():
    message = "hello world"
    assert text_digest(message).hex() == "18426974636f696e205369676e6564204d6573736167653a0a0b68656c6c6f20776f726c64"


def test_bits():
    assert bytes_to_bits(b"\x00") == "00000000"
    assert bytes_to_bits("12") == "00010010"
    assert bytes_to_bits("f1") == "11110001"
    assert bytes_to_bits("0001") == "0000000000000001"

    assert bits_to_bytes("101") == b"\x05"
    assert bits_to_bytes("100010101010111") == b"\x45\x57"
    assert bits_to_bytes("000000000000001") == b"\x00\x01"
    assert bits_to_bytes("0000000000000001") == b"\x00\x01"


def test_get_pushdata_code():
    assert get_pushdata_code(0x4B) == b"\x4b"
    assert get_pushdata_code(0x4C) == bytes.fromhex("4c4c")
    assert get_pushdata_code(0xFF) == bytes.fromhex("4cff")
    assert get_pushdata_code(0x0100) == bytes.fromhex("4d0001")
    assert get_pushdata_code(0xFFFF) == bytes.fromhex("4dffff")
    assert get_pushdata_code(0x010000) == bytes.fromhex("4e00000100")
    assert get_pushdata_code(0x01020304) == bytes.fromhex("4e04030201")

    with pytest.raises(ValueError, match=r"data too long to encode in a PUSHDATA opcode"):
        get_pushdata_code(0x0100000000)


def test_encode_pushdata():
    # minimal push
    assert encode_pushdata(b"") == OpCode.OP_0
    assert encode_pushdata(b"\x00") == b"\x01\x00"
    assert encode_pushdata(b"\x01") == OpCode.OP_1
    assert encode_pushdata(b"\x02") == OpCode.OP_2
    assert encode_pushdata(b"\x10") == OpCode.OP_16
    assert encode_pushdata(b"\x11") == b"\x01\x11"
    assert encode_pushdata(b"\x81") == OpCode.OP_1NEGATE
    # non-minimal push
    with pytest.raises(AssertionError, match=r"empty pushdata"):
        encode_pushdata(b"", False)
    assert encode_pushdata(b"\x00", False) == b"\x01\x00"
    assert encode_pushdata(b"\x01", False) == b"\x01\x01"
    assert encode_pushdata(b"\x02", False) == b"\x01\x02"
    assert encode_pushdata(b"\x10", False) == b"\x01\x10"
    assert encode_pushdata(b"\x11", False) == b"\x01\x11"
    assert encode_pushdata(b"\x81", False) == b"\x01\x81"


def test_encode_int():
    assert encode_int(-2147483648) == bytes.fromhex("05 00 00 00 80 80")
    assert encode_int(-2147483647) == bytes.fromhex("04 FF FF FF FF")
    assert encode_int(-8388608) == bytes.fromhex("04 00 00 80 80")
    assert encode_int(-8388607) == bytes.fromhex("03 FF FF FF")
    assert encode_int(-32768) == bytes.fromhex("03 00 80 80")
    assert encode_int(-32767) == bytes.fromhex("02 FF FF")
    assert encode_int(-128) == bytes.fromhex("02 80 80")
    assert encode_int(-127) == bytes.fromhex("01 FF")
    assert encode_int(-17) == bytes.fromhex("01 91")
    assert encode_int(-16) == bytes.fromhex("01 90")
    assert encode_int(-2) == bytes.fromhex("01 82")
    assert encode_int(-1) == OpCode.OP_1NEGATE

    assert encode_int(0) == OpCode.OP_0

    assert encode_int(1) == OpCode.OP_1
    assert encode_int(2) == OpCode.OP_2
    assert encode_int(16) == OpCode.OP_16
    assert encode_int(17) == bytes.fromhex("01 11")
    assert encode_int(127) == bytes.fromhex("01 7F")
    assert encode_int(128) == bytes.fromhex("02 80 00")
    assert encode_int(32767) == bytes.fromhex("02 FF 7F")
    assert encode_int(32768) == bytes.fromhex("03 00 80 00")
    assert encode_int(8388607) == bytes.fromhex("03 FF FF 7F")
    assert encode_int(8388608) == bytes.fromhex("04 00 00 80 00")
    assert encode_int(2147483647) == bytes.fromhex("04 FF FF FF 7F")
    assert encode_int(2147483648) == bytes.fromhex("05 00 00 00 80 00")


def test_storageutils_uhrp_url():
    import hashlib

    from bsv.storage.utils import UHRP_PREFIX, StorageUtils

    # Normalization
    assert StorageUtils.normalize_url("uhrp://abcdef") == "abcdef"
    assert StorageUtils.normalize_url("web+uhrp://abcdef") == "abcdef"
    assert StorageUtils.normalize_url("other://abcdef") == "other://abcdef"
    # URL generation and validation
    data = b"hello world"
    uhrp_url = StorageUtils.get_url_for_file(data)
    assert uhrp_url.startswith("uhrp://")
    assert StorageUtils.is_valid_url(uhrp_url)
    # Hash extraction matches SHA256
    expected_hash = hashlib.sha256(data).digest()
    actual_hash = StorageUtils.get_hash_from_url(uhrp_url)
    assert actual_hash == expected_hash
    # Invalid prefix - use a valid base58check with wrong prefix
    from bsv.base58 import from_base58check, to_base58check

    # Create a valid base58check with wrong prefix (use a different prefix)
    hash_bytes = b"\x00" * 32
    wrong_prefix = b"\x01\x00"  # Wrong 2-byte prefix
    bad_prefix_url = f"uhrp://{to_base58check(hash_bytes, wrong_prefix)}"
    import pytest

    with pytest.raises(ValueError, match="Bad prefix for UHRP URL"):
        StorageUtils.get_hash_from_url(bad_prefix_url)
    # Invalid length - create valid base58check but with wrong hash length
    short_hash = b"123456789012345678901234567890"  # 30 bytes instead of 32
    bad_url2 = f"uhrp://{to_base58check(short_hash, UHRP_PREFIX)}"
    with pytest.raises(ValueError, match="Invalid hash length in UHRP URL"):
        StorageUtils.get_hash_from_url(bad_url2)
    # is_valid_url returns False for invalid
    assert not StorageUtils.is_valid_url("uhrp://badbase58")
    assert not StorageUtils.is_valid_url(bad_url2)
