# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
import base64, hashlib
from psbt import ser_compact_size
from bech32 import decode
from constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH
from helpers import hash160
from base58 import decode_base58_checksum
from pysecp256k1 import ec_pubkey_serialize
from pysecp256k1.recovery import ecdsa_recover, ecdsa_recoverable_signature_parse_compact
from pysecp256k1.recovery import ecdsa_sign_recoverable, ecdsa_recoverable_signature_serialize_compact


RFC_SIGNATURE_TEMPLATE = '''\
-----BEGIN BITCOIN SIGNED MESSAGE-----
{msg}
-----BEGIN BITCOIN SIGNATURE-----
{addr}
{sig}
-----END BITCOIN SIGNATURE-----
'''


def parse_signed_message(msg):
    msplit = msg.strip().rsplit("\n", 4)
    assert msplit[0].startswith("-----BEGIN BITCOIN SIGNED MESSAGE-----\n")
    msg = msplit[0].replace("-----BEGIN BITCOIN SIGNED MESSAGE-----\n", "")
    assert msplit[1] == "-----BEGIN BITCOIN SIGNATURE-----"
    assert msplit[4] == "-----END BITCOIN SIGNATURE-----"
    return msg, msplit[2], msplit[3]


def sig_hdr_base(addr_fmt):
    if addr_fmt in (AF_CLASSIC, "p2pkh"):
        return 31
    elif addr_fmt in (AF_P2WPKH_P2SH, "p2sh-p2wpkh", "p2wpkh-p2sh"):
        return 35
    elif addr_fmt in (AF_P2WPKH, "p2wpkh"):
        return 39
    else:
        raise ValueError


def bitcoin_hash_message(msg: bytes):
    s = hashlib.sha256()
    s.update(b'\x18Bitcoin Signed Message:\n')
    msg_len = len(msg)
    s.update(ser_compact_size(msg_len))
    s.update(msg)
    return hashlib.sha256(s.digest()).digest()


def sign_message(sk, msg, addr_fmt=AF_CLASSIC, b64=True):
    sig_o = ecdsa_sign_recoverable(sk, bitcoin_hash_message(msg))
    sig_bytes, rec_id = ecdsa_recoverable_signature_serialize_compact(sig_o)
    header_byte = rec_id + sig_hdr_base(addr_fmt=addr_fmt)
    sig = bytes([header_byte]) + sig_bytes
    if b64:
        return base64.b64encode(sig).decode().strip()
    return sig


def verify_message(address, signature, message):
    script = None
    success = False
    h160 = None
    sig_bytes = base64.b64decode(signature)
    rec_id = (sig_bytes[0] - 27) & 0x03 # least two significant bits
    msg_hash = bitcoin_hash_message(message.encode())
    sig = ecdsa_recoverable_signature_parse_compact(sig_bytes[1:], rec_id)
    pubkey = ecdsa_recover(sig, msg_hash)
    rec_pk = ec_pubkey_serialize(pubkey)
    rec_pk_h160 = hash160(rec_pk)
    if address.startswith(("1", "m", "n")):
        raw = decode_base58_checksum(address)
        h160 = raw[1:]
    elif address.startswith(("3", "2")):
        raw = decode_base58_checksum(address)
        script = raw[1:]
    elif address.startswith(("bc1q", "tb1q", "bcrt1q")):
        raw = decode("bcrt" if address[:4] == "bcrt" else address[:2], address)
        if len(raw[1]) != 20:
            raise ValueError("p2wsh")
        h160 = bytes(raw[1])
    else:
        raise ValueError("Unsupported address format")

    if script:
        target = bytes([0, 20]) + rec_pk_h160
        target = hash160(target)
        if target == script:
            success = True
    else:
        if rec_pk_h160 == h160:
            success = True
    return success
