from micropython import const

from trezor.crypto import bech32
from trezor.crypto.scripts import sha256_ripemd160_digest
from trezor.messages.BinanceCancelMsg import BinanceCancelMsg
from trezor.messages.BinanceInputOutput import BinanceInputOutput
from trezor.messages.BinanceOrderMsg import BinanceOrderMsg
from trezor.messages.BinanceSignTx import BinanceSignTx
from trezor.messages.BinanceTransferMsg import BinanceTransferMsg

from apps.common import HARDENED

ENVELOPE_BLUEPRINT = '{{"account_number":"{account_number}","chain_id":"{chain_id}","data":null,"memo":"{memo}","msgs":[{msgs}],"sequence":"{sequence}","source":"{source}"}}'
MSG_TRANSFER_BLUEPRINT = '{{"inputs":[{inputs}],"outputs":[{outputs}]}}'
MSG_NEWORDER_BLUEPRINT = '{{"id":"{id}","ordertype":{ordertype},"price":{price},"quantity":{quantity},"sender":"{sender}","side":{side},"symbol":"{symbol}","timeinforce":{timeinforce}}}'
MSG_CANCEL_BLUEPRINT = '{{"refid":"{refid}","sender":"{sender}","symbol":"{symbol}"}}'
INPUT_OUTPUT_BLUEPRINT = '{{"address":"{address}","coins":[{coins}]}}'
COIN_BLUEPRINT = '{{"amount":{amount},"denom":"{denom}"}}'

# 1*10^8 Jagers equal 1 BNB https://www.binance.vision/glossary/jager
DIVISIBILITY = const(8)


def produce_json_for_signing(envelope: BinanceSignTx, msg) -> str:
    if isinstance(msg, BinanceTransferMsg):
        json_msg = produce_transfer_json(msg)
    elif isinstance(msg, BinanceOrderMsg):
        json_msg = produce_neworder_json(msg)
    elif isinstance(msg, BinanceCancelMsg):
        json_msg = produce_cancel_json(msg)
    else:
        raise ValueError("input message unrecognized, is of type " + type(msg).__name__)

    if envelope.source is None or envelope.source < 0:
        raise ValueError("source missing or invalid")

    source = envelope.source

    return ENVELOPE_BLUEPRINT.format(
        account_number=envelope.account_number,
        chain_id=envelope.chain_id,
        memo=envelope.memo,
        msgs=json_msg,
        sequence=envelope.sequence,
        source=source,
    )


def produce_transfer_json(msg: BinanceTransferMsg) -> str:
    def make_input_output(input_output: BinanceInputOutput):
        coins = ",".join(
            COIN_BLUEPRINT.format(amount=c.amount, denom=c.denom)
            for c in input_output.coins
        )
        return INPUT_OUTPUT_BLUEPRINT.format(address=input_output.address, coins=coins)

    inputs = ",".join(make_input_output(i) for i in msg.inputs)
    outputs = ",".join(make_input_output(o) for o in msg.outputs)

    return MSG_TRANSFER_BLUEPRINT.format(inputs=inputs, outputs=outputs)


def produce_neworder_json(msg: BinanceOrderMsg) -> str:
    return MSG_NEWORDER_BLUEPRINT.format(
        id=msg.id,
        ordertype=msg.ordertype,
        price=msg.price,
        quantity=msg.quantity,
        sender=msg.sender,
        side=msg.side,
        symbol=msg.symbol,
        timeinforce=msg.timeinforce,
    )


def produce_cancel_json(msg: BinanceCancelMsg) -> str:
    return MSG_CANCEL_BLUEPRINT.format(
        refid=msg.refid, sender=msg.sender, symbol=msg.symbol
    )


def address_from_public_key(pubkey: bytes, hrp: str) -> str:
    """
    Address = RIPEMD160(SHA256(compressed public key))
    Address_Bech32 = HRP + '1' + bech32.encode(convert8BitsTo5Bits(RIPEMD160(SHA256(compressed public key))))
    HRP - bnb for productions, tbnb for tests
    """

    h = sha256_ripemd160_digest(pubkey)

    convertedbits = bech32.convertbits(h, 8, 5, False)

    return bech32.bech32_encode(hrp, convertedbits)


def validate_full_path(path: list) -> bool:
    """
    Validates derivation path to equal 44'/714'/a'/0/0,
    where `a` is an account index from 0 to 1 000 000.
    Similar to Ethereum this should be 44'/714'/a', but for
    compatibility with other HW vendors we use 44'/714'/a'/0/0.
    """
    if len(path) != 5:
        return False
    if path[0] != 44 | HARDENED:
        return False
    if path[1] != 714 | HARDENED:
        return False
    if path[2] < HARDENED or path[2] > 1000000 | HARDENED:
        return False
    if path[3] != 0:
        return False
    if path[4] != 0:
        return False
    return True
