from typing import TYPE_CHECKING

from trezor.enums import ButtonRequestType
from trezor.ui.layouts import confirm_properties

from ..helpers import eos_asset_to_string, eos_name_to_string

if TYPE_CHECKING:
    from typing import Iterable
    from trezor.wire import Context
    from trezor.messages import (
        EosActionBuyRam,
        EosActionBuyRamBytes,
        EosActionCommon,
        EosActionDelegate,
        EosActionDeleteAuth,
        EosActionLinkAuth,
        EosActionNewAccount,
        EosActionRefund,
        EosActionSellRam,
        EosActionTransfer,
        EosActionUndelegate,
        EosActionUnlinkAuth,
        EosActionUpdateAuth,
        EosActionVoteProducer,
        EosAuthorization,
    )
    from trezor.ui.layouts import PropertyType


# Whether action is last - and should be HoldToConfirm
is_last = False


# Because icon and br_code are almost always the same
# (and also calling with positional arguments takes less space)
async def _confirm_properties(
    ctx: Context,
    br_type: str,
    title: str,
    props: Iterable[PropertyType],
) -> None:
    await confirm_properties(
        ctx,
        br_type,
        title,
        props,
        hold=is_last,
        br_code=ButtonRequestType.ConfirmOutput,
    )


async def confirm_action_buyram(ctx: Context, msg: EosActionBuyRam) -> None:
    await _confirm_properties(
        ctx,
        "confirm_buyram",
        "Buy RAM",
        (
            ("Payer:", eos_name_to_string(msg.payer)),
            ("Receiver:", eos_name_to_string(msg.receiver)),
            ("Amount:", eos_asset_to_string(msg.quantity)),
        ),
    )


async def confirm_action_buyrambytes(ctx: Context, msg: EosActionBuyRamBytes) -> None:
    await _confirm_properties(
        ctx,
        "confirm_buyrambytes",
        "Buy RAM",
        (
            ("Payer:", eos_name_to_string(msg.payer)),
            ("Receiver:", eos_name_to_string(msg.receiver)),
            ("Bytes:", str(msg.bytes)),
        ),
    )


async def confirm_action_delegate(ctx: Context, msg: EosActionDelegate) -> None:
    props = [
        ("Sender:", eos_name_to_string(msg.sender)),
        ("Receiver:", eos_name_to_string(msg.receiver)),
        ("CPU:", eos_asset_to_string(msg.cpu_quantity)),
        ("NET:", eos_asset_to_string(msg.net_quantity)),
    ]
    append = props.append  # local_cache_attribute
    if msg.transfer:
        append(("Transfer:", "Yes"))
        append(("Receiver:", eos_name_to_string(msg.receiver)))
    else:
        append(("Transfer:", "No"))

    await _confirm_properties(
        ctx,
        "confirm_delegate",
        "Delegate",
        props,
    )


async def confirm_action_sellram(ctx: Context, msg: EosActionSellRam) -> None:
    await _confirm_properties(
        ctx,
        "confirm_sellram",
        "Sell RAM",
        (
            ("Receiver:", eos_name_to_string(msg.account)),
            ("Bytes:", str(msg.bytes)),
        ),
    )


async def confirm_action_undelegate(ctx: Context, msg: EosActionUndelegate) -> None:
    await _confirm_properties(
        ctx,
        "confirm_undelegate",
        "Undelegate",
        (
            ("Sender:", eos_name_to_string(msg.sender)),
            ("Receiver:", eos_name_to_string(msg.receiver)),
            ("CPU:", eos_asset_to_string(msg.cpu_quantity)),
            ("NET:", eos_asset_to_string(msg.net_quantity)),
        ),
    )


async def confirm_action_refund(ctx: Context, msg: EosActionRefund) -> None:
    await _confirm_properties(
        ctx,
        "confirm_refund",
        "Refund",
        (("Owner:", eos_name_to_string(msg.owner)),),
    )


async def confirm_action_voteproducer(ctx: Context, msg: EosActionVoteProducer) -> None:
    producers = msg.producers  # local_cache_attribute

    if msg.proxy and not producers:
        # PROXY
        await _confirm_properties(
            ctx,
            "confirm_voteproducer",
            "Vote for proxy",
            (
                ("Voter:", eos_name_to_string(msg.voter)),
                ("Proxy:", eos_name_to_string(msg.proxy)),
            ),
        )

    elif producers:
        # PRODUCERS
        await _confirm_properties(
            ctx,
            "confirm_voteproducer",
            "Vote for producers",
            (
                (f"{wi:2d}. {eos_name_to_string(producer)}", None)
                for wi, producer in enumerate(producers, 1)
            ),
        )

    else:
        # Cancel vote
        await _confirm_properties(
            ctx,
            "confirm_voteproducer",
            "Cancel vote",
            (("Voter:", eos_name_to_string(msg.voter)),),
        )


async def confirm_action_transfer(
    ctx: Context, msg: EosActionTransfer, account: str
) -> None:
    props = [
        ("From:", eos_name_to_string(msg.sender)),
        ("To:", eos_name_to_string(msg.receiver)),
        ("Amount:", eos_asset_to_string(msg.quantity)),
        ("Contract:", account),
    ]
    if msg.memo is not None:
        props.append(("Memo", msg.memo[:512]))
    await _confirm_properties(
        ctx,
        "confirm_transfer",
        "Transfer",
        props,
    )


async def confirm_action_updateauth(ctx: Context, msg: EosActionUpdateAuth) -> None:
    props: list[PropertyType] = [
        ("Account:", eos_name_to_string(msg.account)),
        ("Permission:", eos_name_to_string(msg.permission)),
        ("Parent:", eos_name_to_string(msg.parent)),
    ]
    props.extend(authorization_fields(msg.auth))
    await _confirm_properties(
        ctx,
        "confirm_updateauth",
        "Update Auth",
        props,
    )


async def confirm_action_deleteauth(ctx: Context, msg: EosActionDeleteAuth) -> None:
    await _confirm_properties(
        ctx,
        "confirm_deleteauth",
        "Delete Auth",
        (
            ("Account:", eos_name_to_string(msg.account)),
            ("Permission:", eos_name_to_string(msg.permission)),
        ),
    )


async def confirm_action_linkauth(ctx: Context, msg: EosActionLinkAuth) -> None:
    await _confirm_properties(
        ctx,
        "confirm_linkauth",
        "Link Auth",
        (
            ("Account:", eos_name_to_string(msg.account)),
            ("Code:", eos_name_to_string(msg.code)),
            ("Type:", eos_name_to_string(msg.type)),
            ("Requirement:", eos_name_to_string(msg.requirement)),
        ),
    )


async def confirm_action_unlinkauth(ctx: Context, msg: EosActionUnlinkAuth) -> None:
    await _confirm_properties(
        ctx,
        "confirm_unlinkauth",
        "Unlink Auth",
        (
            ("Account:", eos_name_to_string(msg.account)),
            ("Code:", eos_name_to_string(msg.code)),
            ("Type:", eos_name_to_string(msg.type)),
        ),
    )


async def confirm_action_newaccount(ctx: Context, msg: EosActionNewAccount) -> None:
    props: list[PropertyType] = [
        ("Creator:", eos_name_to_string(msg.creator)),
        ("Name:", eos_name_to_string(msg.name)),
    ]
    props.extend(authorization_fields(msg.owner))
    props.extend(authorization_fields(msg.active))
    await _confirm_properties(
        ctx,
        "confirm_newaccount",
        "New Account",
        props,
    )


async def confirm_action_unknown(
    ctx: Context, action: EosActionCommon, checksum: bytes
) -> None:
    await confirm_properties(
        ctx,
        "confirm_unknown",
        "Arbitrary data",
        (
            ("Contract:", eos_name_to_string(action.account)),
            ("Action Name:", eos_name_to_string(action.name)),
            ("Checksum:", checksum),
        ),
        hold=is_last,
        br_code=ButtonRequestType.ConfirmOutput,
    )


def authorization_fields(auth: EosAuthorization) -> list[PropertyType]:
    from trezor.wire import DataError
    from ..helpers import public_key_to_wif

    fields: list[PropertyType] = []
    append = fields.append  # local_cache_attribute

    append(("Threshold:", str(auth.threshold)))

    # NOTE: getting rid of f-strings saved almost 100 bytes

    for i, key in enumerate(auth.keys, 1):
        if key.key is None:
            raise DataError("Key must be provided explicitly.")

        _key = public_key_to_wif(bytes(key.key))
        _weight = str(key.weight)

        header = "Key #" + str(i) + ":"
        w_header = "Key #" + str(i) + " Weight:"

        append((header, _key))
        append((w_header, _weight))

    for i, account in enumerate(auth.accounts, 1):
        _account = eos_name_to_string(account.account.actor)
        _permission = eos_name_to_string(account.account.permission)

        i = str(i)
        a_header = "Account #" + i + ":"
        p_header = "Acc Permission #" + i + ":"
        w_header = "Account #" + i + " weight:"

        append((a_header, _account))
        append((p_header, _permission))
        append((w_header, str(account.weight)))

    for i, wait in enumerate(auth.waits, 1):
        _wait = str(wait.wait_sec)
        _weight = str(wait.weight)

        header = "Delay #" + str(i)
        w_header = header + " weight:"
        append((header, _wait + " sec"))
        append((w_header, _weight))

    return fields
