import click

from wton.tonsdk.contract.wallet import SendModeEnum, Wallets
from ._base_cmd import cli, SharedObject
from .._utils import CustomClickException, with_whitelist, click_ton_exception_handler, \
    click_echo_success, with_keystore, y_n_to_bool
from wton.tonclient.utils import RecordDoesNotExistError, KeyStoreInvalidPasswordError
from wton.tonclient import ton_exceptions_handler
import importlib.util
import sys


# TODO: optimize code, this part was developed fast

@cli.group()
def dev():
    """
    Development tools.
    """


@dev.command()
@ton_exceptions_handler(click_ton_exception_handler)
@with_keystore(password_required=True)
@click.argument('python_script_path', required=True, type=click.Path(exists=True))
@click.argument('method_to_call', required=True)
@click.argument('from_wallet', required=True)
@click.argument('amount', required=False, metavar='TON_COINS_NUM')
@click.option('--wait', '-w', is_flag=True, help='Wait until transaction is committed.', default=False)
@click.option('--bounceable', default="y", type=click.Choice(["y", "n"]),
              show_default=True, callback=y_n_to_bool,
              help="Bounce back on errors. Should be 'n' to send money to an empty address.")
@click.option('--pay-gas-separately', default="y", type=click.Choice(["y", "n"]), show_default=True)
@click.option('--ignore-errors', default="n", type=click.Choice(["y", "n"]), show_default=True,
              help='Bounce back if error occurs.')
@click.option('--destroy-if-zero', default="n", type=click.Choice(["y", "n"]), show_default=True)
@click.option('--transfer-all', default="n", type=click.Choice(["y", "n"]), show_default=True)
@click.pass_obj
def send_internal(shared_object: SharedObject, from_wallet, python_script_path, method_to_call, amount,
                  wait, bounceable, pay_gas_separately, ignore_errors, destroy_if_zero, transfer_all):
    """
    Generate internal message by METHOD_TO_CALL from PYTHON_SCRIPT_PATH
    and send it by selected FROM_WALLET wallet.
    """
    if amount is None and not transfer_all:
        raise CustomClickException(
            "You must specify amount when you do not use --transfer-all flag.")

    try:
        record = shared_object.keystore.get_record_by_name(
            from_wallet, raise_none=True)
        mnemonics = shared_object.keystore.get_secret(
            record, shared_object.keystore_password).split(" ")
    except (RecordDoesNotExistError,
            KeyStoreInvalidPasswordError) as e:
        raise CustomClickException(e)

    send_mode = 0
    if ignore_errors == "y":
        send_mode |= SendModeEnum.ignore_errors
    if pay_gas_separately == "y":
        send_mode |= SendModeEnum.pay_gas_separately
    if destroy_if_zero == "y":
        send_mode |= SendModeEnum.destroy_account_if_zero
    if transfer_all == "y":
        send_mode |= SendModeEnum.carry_all_remaining_balance

    _mnemonics, _pub_k, _priv_k, wallet = Wallets.from_mnemonics(mnemonics, record.version,
                                                                 record.workchain)

    spec = importlib.util.spec_from_file_location("module.name", python_script_path)
    module = importlib.util.module_from_spec(spec)
    sys.modules["module.name"] = module
    spec.loader.exec_module(module)
    addr, state_init, body = getattr(module, method_to_call)(wallet)

    result = shared_object.ton_client.transfer(
        wallet, addr, amount, body, send_mode, bounceable, wait, state_init)

    click_echo_success(
        f"Internal message generated by {method_to_call} was sent to address {addr}. Result: {result}")


@dev.command()
@ton_exceptions_handler(click_ton_exception_handler)
@click.argument('python_script_path', required=True, type=click.Path(exists=True))
@click.argument('method_to_call', required=True)
@click.option('--wait', '-w', is_flag=True, help='Wait until transaction is committed.', default=False)
@click.pass_obj
def send_external(shared_object: SharedObject, python_script_path, method_to_call, wait):
    """
    Generate external message by METHOD_TO_CALL from PYTHON_SCRIPT_PATH and send it as an external message.
    """
    spec = importlib.util.spec_from_file_location("module.name", python_script_path)
    module = importlib.util.module_from_spec(spec)
    sys.modules["module.name"] = module
    spec.loader.exec_module(module)
    dest, message_cell = getattr(module, method_to_call)()
    result = shared_object.ton_client.send_boc(message_cell.to_boc(False), wait)

    click_echo_success(
        f"External message generated by {method_to_call} was sent to address {dest}. Result: {result}")


@dev.command()
@ton_exceptions_handler(click_ton_exception_handler)
@click.argument('boc_file', required=True, type=click.Path(exists=True))
@click.option('--wait', '-w', is_flag=True, help='Wait until transaction is committed.', default=False)
@click.pass_obj
def send_boc(shared_object: SharedObject, boc_file: str, wait: bool):
    """
    Send boc from the binary BOC_FILE.
    """
    with open(boc_file, "rb") as f:
        result = shared_object.ton_client.send_boc(f.read(), wait)

    click_echo_success(f"{result}")
