import pytest
from pathlib import Path
from ragger.backend import BackendInterface
from ragger.firmware import Firmware
from ragger.error import ExceptionRAPDU
from ragger.navigator import Navigator, NavInsID

import ledger_app_clients.ethereum.response_parser as ResponseParser
from ledger_app_clients.ethereum.client import EthAppClient, StatusWord
from ledger_app_clients.ethereum.settings import SettingID, settings_toggle

from web3 import Web3


ROOT_SCREENSHOT_PATH = Path(__file__).parent

# Values used across all tests
CHAIN_ID = 1
NAME = "ledger.eth"
ADDR = bytes.fromhex("0011223344556677889900112233445566778899")
KEY_ID = 1
ALGO_ID = 1
BIP32_PATH = "m/44'/60'/0'/0/0"
NONCE = 21
GAS_PRICE = 13
GAS_LIMIT = 21000
AMOUNT = 1.22


@pytest.fixture(params=[False, True])
def verbose(request) -> bool:
    return request.param


def common(app_client: EthAppClient) -> int:
    if app_client._client.firmware.device == "nanos":
        pytest.skip("Not supported on LNS")
    with app_client.get_challenge():
        pass
    return ResponseParser.challenge(app_client.response().data)


def test_send_fund(firmware: Firmware,
                   backend: BackendInterface,
                   navigator: Navigator,
                   test_name: str,
                   verbose: bool):
    app_client = EthAppClient(backend)
    challenge = common(app_client)

    if verbose:
        settings_toggle(firmware, navigator, [SettingID.VERBOSE_ENS])

    with app_client.provide_domain_name(challenge, NAME, ADDR):
        pass

    with app_client.sign(BIP32_PATH,
                         {
                             "nonce": NONCE,
                             "gasPrice": Web3.to_wei(GAS_PRICE, "gwei"),
                             "gas": GAS_LIMIT,
                             "to": ADDR,
                             "value": Web3.to_wei(AMOUNT, "ether"),
                             "chainId": CHAIN_ID
                         }):
        moves = list()
        if firmware.device.startswith("nano"):
            moves += [NavInsID.RIGHT_CLICK] * 4
            if verbose:
                moves += [NavInsID.RIGHT_CLICK]
            moves += [NavInsID.BOTH_CLICK]
        else:
            moves += [NavInsID.USE_CASE_REVIEW_TAP] * 2
            if verbose:
                moves += [NavInsID.USE_CASE_REVIEW_TAP]
            moves += [NavInsID.USE_CASE_REVIEW_CONFIRM]
        navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH,
                                       "domain_name_verbose_" + str(verbose),
                                       moves)


def test_send_fund_wrong_challenge(firmware: Firmware,
                                   backend: BackendInterface,
                                   navigator: Navigator):
    app_client = EthAppClient(backend)
    challenge = common(app_client)

    try:
        with app_client.provide_domain_name(~challenge & 0xffffffff, NAME, ADDR):
            pass
    except ExceptionRAPDU as e:
        assert e.status == StatusWord.INVALID_DATA
    else:
        assert False  # An exception should have been raised


def test_send_fund_wrong_addr(firmware: Firmware,
                              backend: BackendInterface,
                              navigator: Navigator,
                              test_name: str):
    app_client = EthAppClient(backend)
    challenge = common(app_client)

    with app_client.provide_domain_name(challenge, NAME, ADDR):
        pass

    addr = bytearray(ADDR)
    addr.reverse()

    with app_client.sign(BIP32_PATH,
                         {
                             "nonce": NONCE,
                             "gasPrice": Web3.to_wei(GAS_PRICE, "gwei"),
                             "gas": GAS_LIMIT,
                             "to": bytes(addr),
                             "value": Web3.to_wei(AMOUNT, "ether"),
                             "chainId": CHAIN_ID
                         }):
        moves = list()
        if firmware.device.startswith("nano"):
            moves += [NavInsID.RIGHT_CLICK] * 4
            moves += [NavInsID.BOTH_CLICK]
        else:
            moves += [NavInsID.USE_CASE_REVIEW_TAP] * 2
            moves += [NavInsID.USE_CASE_REVIEW_CONFIRM]
        navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH,
                                       "domain_name_wrong_addr",
                                       moves)


def test_send_fund_non_mainnet(firmware: Firmware,
                               backend: BackendInterface,
                               navigator: Navigator,
                               test_name: str):
    app_client = EthAppClient(backend)
    challenge = common(app_client)

    with app_client.provide_domain_name(challenge, NAME, ADDR):
        pass

    with app_client.sign(BIP32_PATH,
                         {
                             "nonce": NONCE,
                             "gasPrice": Web3.to_wei(GAS_PRICE, "gwei"),
                             "gas": GAS_LIMIT,
                             "to": ADDR,
                             "value": Web3.to_wei(AMOUNT, "ether"),
                             "chainId": 5
                         }):
        moves = list()
        if firmware.device.startswith("nano"):
            moves += [NavInsID.RIGHT_CLICK] * 5
            moves += [NavInsID.BOTH_CLICK]
        else:
            moves += [NavInsID.USE_CASE_REVIEW_TAP] * 2
            moves += [NavInsID.USE_CASE_REVIEW_CONFIRM]
        navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH,
                                       "domain_name_non_mainnet",
                                       moves)


def test_send_fund_unknown_chain(firmware: Firmware,
                                 backend: BackendInterface,
                                 navigator: Navigator,
                                 test_name: str):
    app_client = EthAppClient(backend)
    challenge = common(app_client)

    with app_client.provide_domain_name(challenge, NAME, ADDR):
        pass

    with app_client.sign(BIP32_PATH,
                         {
                             "nonce": NONCE,
                             "gasPrice": Web3.to_wei(GAS_PRICE, "gwei"),
                             "gas": GAS_LIMIT,
                             "to": ADDR,
                             "value": Web3.to_wei(AMOUNT, "ether"),
                             "chainId": 9
                         }):
        moves = list()
        if firmware.device.startswith("nano"):
            moves += [NavInsID.RIGHT_CLICK] * 5
            moves += [NavInsID.BOTH_CLICK]
        else:
            moves += [NavInsID.USE_CASE_REVIEW_TAP] * 3
            moves += [NavInsID.USE_CASE_REVIEW_CONFIRM]
        navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH,
                                       "domain_name_unknown_chain",
                                       moves)


def test_send_fund_domain_too_long(firmware: Firmware,
                                   backend: BackendInterface,
                                   navigator: Navigator):
    app_client = EthAppClient(backend)
    challenge = common(app_client)

    try:
        with app_client.provide_domain_name(challenge, "ledger" + "0"*25 + ".eth", ADDR):
            pass
    except ExceptionRAPDU as e:
        assert e.status == StatusWord.INVALID_DATA
    else:
        assert False  # An exception should have been raised


def test_send_fund_domain_invalid_character(firmware: Firmware,
                                            backend: BackendInterface,
                                            navigator: Navigator):
    app_client = EthAppClient(backend)
    challenge = common(app_client)

    try:
        with app_client.provide_domain_name(challenge, "l\xe8dger.eth", ADDR):
            pass
    except ExceptionRAPDU as e:
        assert e.status == StatusWord.INVALID_DATA
    else:
        assert False  # An exception should have been raised


def test_send_fund_uppercase(firmware: Firmware,
                             backend: BackendInterface,
                             navigator: Navigator):
    app_client = EthAppClient(backend)
    challenge = common(app_client)

    try:
        with app_client.provide_domain_name(challenge, NAME.upper(), ADDR):
            pass
    except ExceptionRAPDU as e:
        assert e.status == StatusWord.INVALID_DATA
    else:
        assert False  # An exception should have been raised


def test_send_fund_domain_non_ens(firmware: Firmware,
                                  backend: BackendInterface,
                                  navigator: Navigator):
    app_client = EthAppClient(backend)
    challenge = common(app_client)

    try:
        with app_client.provide_domain_name(challenge, "ledger.hte", ADDR):
            pass
    except ExceptionRAPDU as e:
        assert e.status == StatusWord.INVALID_DATA
    else:
        assert False  # An exception should have been raised
