from typing import TYPE_CHECKING

from trezor import TR
from trezor.crypto import base58
from trezor.enums import ButtonRequestType
from trezor.strings import format_amount
from trezor.ui.layouts import (
    confirm_metadata,
    confirm_properties,
    confirm_solana_tx,
    confirm_value,
)

from apps.common.paths import address_n_to_str

from .types import AddressType

if TYPE_CHECKING:
    from .transaction.instructions import Instruction, SystemProgramTransferInstruction
    from .types import AddressReference


def _format_path(path: list[int]) -> str:
    from micropython import const

    from apps.common.paths import unharden

    if len(path) < 4:
        return address_n_to_str(path)

    ACCOUNT_PATH_INDEX = const(3)
    account_index = path[ACCOUNT_PATH_INDEX]
    return f"Solana #{unharden(account_index) + 1}"


def _get_address_reference_props(address: AddressReference, display_name: str):
    return (
        (TR.solana__is_provided_via_lookup_table_template.format(display_name), ""),
        (f"{TR.solana__lookup_table_address}:", base58.encode(address[0])),
        (f"{TR.solana__account_index}:", f"{address[1]}"),
    )


async def confirm_instruction(
    instruction: Instruction,
    instructions_count: int,
    instruction_index: int,
    signer_path: list[int],
    signer_public_key: bytes,
) -> None:
    instruction_title = (
        f"{instruction_index}/{instructions_count}: {instruction.ui_name}"
    )

    if instruction.is_deprecated_warning is not None:
        await confirm_metadata(
            "confirm_deprecated_warning",
            instruction_title,
            instruction.is_deprecated_warning,
            br_code=ButtonRequestType.Other,
        )

    if instruction.multisig_signers:
        await confirm_metadata(
            "confirm_multisig",
            TR.solana__confirm_multisig,
            TR.solana__instruction_is_multisig,
            br_code=ButtonRequestType.Other,
        )

    for ui_property in instruction.ui_properties:
        if ui_property.parameter is not None:
            property_template = instruction.get_property_template(ui_property.parameter)
            value = instruction.parsed_data[ui_property.parameter]

            if property_template.is_authority and signer_public_key == value:
                continue

            if property_template.is_optional and value is None:
                continue

            if ui_property.default_value_to_hide == value:
                continue

            await confirm_properties(
                "confirm_instruction",
                f"{instruction_index}/{instructions_count}: {instruction.ui_name}",
                (
                    (
                        ui_property.display_name,
                        property_template.format(instruction, value),
                    ),
                ),
            )
        elif ui_property.account is not None:
            account_template = instruction.get_account_template(ui_property.account)

            # optional account, skip if not present
            if ui_property.account not in instruction.parsed_accounts:
                continue

            account_value = instruction.parsed_accounts[ui_property.account]

            if account_template.is_authority:
                if signer_public_key == account_value[0]:
                    continue

            account_data: list[tuple[str, str]] = []
            if len(account_value) == 2:
                signer_suffix = ""
                if account_value[0] == signer_public_key:
                    signer_suffix = f" ({TR.words__signer})"

                account_data.append(
                    (
                        ui_property.display_name,
                        f"{base58.encode(account_value[0])}{signer_suffix}",
                    )
                )
            elif len(account_value) == 3:
                account_data += _get_address_reference_props(
                    account_value, ui_property.display_name
                )
            else:
                raise ValueError  # Invalid account value

            await confirm_properties(
                "confirm_instruction",
                f"{instruction_index}/{instructions_count}: {instruction.ui_name}",
                account_data,
            )
        else:
            raise ValueError  # Invalid ui property

    if instruction.multisig_signers:
        signers: list[tuple[str, str]] = []
        for i, multisig_signer in enumerate(instruction.multisig_signers, 1):
            multisig_signer_public_key = multisig_signer[0]

            path_str = ""
            if multisig_signer_public_key == signer_public_key:
                path_str = f" ({address_n_to_str(signer_path)})"

            signers.append(
                (
                    f"{TR.words__signer} {i}{path_str}:",
                    base58.encode(multisig_signer[0]),
                )
            )

        await confirm_properties(
            "confirm_instruction",
            f"{instruction_index}/{instructions_count}: {instruction.ui_name}",
            signers,
        )


def get_address_type(address_type: AddressType) -> str:
    if address_type == AddressType.AddressSig:
        return f"({TR.words__writable}, {TR.words__signer})"
    if address_type == AddressType.AddressSigReadOnly:
        return f"({TR.words__signer})"
    if address_type == AddressType.AddressReadOnly:
        return ""
    if address_type == AddressType.AddressRw:
        return f"({TR.words__writable})"
    raise ValueError  # Invalid address type


async def confirm_unsupported_instruction_details(
    instruction: Instruction,
    title: str,
    signer_path: list[int],
    signer_public_key: bytes,
) -> None:
    from trezor.ui import NORMAL
    from trezor.ui.layouts import confirm_properties, should_show_more

    should_show_instruction_details = await should_show_more(
        title,
        (
            (
                NORMAL,
                TR.solana__instruction_accounts_template.format(
                    len(instruction.accounts), len(instruction.instruction_data)
                ),
            ),
        ),
        TR.buttons__show_details,
        confirm=TR.buttons__continue,
    )

    if should_show_instruction_details:
        await confirm_properties(
            "instruction_data",
            title,
            ((f"{TR.solana__instruction_data}:", bytes(instruction.instruction_data)),),
        )

        accounts = []
        for i, account in enumerate(instruction.accounts, 1):
            if len(account) == 2:
                account_public_key = account[0]
                address_type = get_address_type(account[1])

                path_str = ""
                if account_public_key == signer_public_key:
                    path_str = f" ({address_n_to_str(signer_path)})"

                accounts.append(
                    (
                        f"{TR.words__account} {i}{path_str} {address_type}:",
                        base58.encode(account_public_key),
                    )
                )
            elif len(account) == 3:
                address_type = get_address_type(account[2])
                accounts += _get_address_reference_props(
                    account, f"{TR.words__account} {i} {address_type}"
                )
            else:
                raise ValueError  # Invalid account value

        await confirm_properties(
            "accounts",
            title,
            accounts,
        )


async def confirm_unsupported_instruction_confirm(
    instruction: Instruction,
    instructions_count: int,
    instruction_index: int,
    signer_path: list[int],
    signer_public_key: bytes,
) -> None:
    formatted_instruction_id = (
        instruction.instruction_id if instruction.instruction_id is not None else "N/A"
    )
    title = f"{instruction_index}/{instructions_count}: {instruction.ui_name}: instruction id ({formatted_instruction_id})"

    return await confirm_unsupported_instruction_details(
        instruction, title, signer_path, signer_public_key
    )


async def confirm_unsupported_program_confirm(
    instruction: Instruction,
    instructions_count: int,
    instruction_index: int,
    signer_path: list[int],
    signer_public_key: bytes,
) -> None:
    title = f"{instruction_index}/{instructions_count}: {instruction.ui_name}"

    return await confirm_unsupported_instruction_details(
        instruction, title, signer_path, signer_public_key
    )


async def confirm_system_transfer(
    transfer_instruction: SystemProgramTransferInstruction,
    fee: int,
    signer_path: list[int],
    blockhash: bytes,
) -> None:
    await confirm_value(
        title=TR.words__recipient,
        value=base58.encode(transfer_instruction.recipient_account[0]),
        description="",
        br_name="confirm_recipient",
        br_code=ButtonRequestType.ConfirmOutput,
        verb=TR.buttons__continue,
    )

    await confirm_custom_transaction(
        transfer_instruction.lamports,
        9,
        "SOL",
        fee,
        signer_path,
        blockhash,
    )


async def confirm_token_transfer(
    destination_account: bytes,
    token_account: bytes,
    token_mint: bytes,
    amount: int,
    decimals: int,
    fee: int,
    signer_path: list[int],
    blockhash: bytes,
):
    await confirm_value(
        title=TR.words__recipient,
        value=base58.encode(destination_account),
        description="",
        br_name="confirm_recipient",
        br_code=ButtonRequestType.ConfirmOutput,
        verb=TR.buttons__continue,
        info_items=(
            ((f"{TR.solana__associated_token_account}:", base58.encode(token_account)),)
            if token_account != destination_account
            else None
        ),
    )

    await confirm_value(
        title=TR.solana__token_address,
        value=base58.encode(token_mint),
        description="",
        br_name="confirm_token_address",
        br_code=ButtonRequestType.ConfirmOutput,
        verb=TR.buttons__continue,
    )

    await confirm_custom_transaction(
        amount,
        decimals,
        "[TOKEN]",
        fee,
        signer_path,
        blockhash,
    )


async def confirm_custom_transaction(
    amount: int,
    decimals: int,
    unit: str,
    fee: int,
    signer_path: list[int],
    blockhash: bytes,
) -> None:
    await confirm_solana_tx(
        amount=f"{format_amount(amount, decimals)} {unit}",
        fee=f"{format_amount(fee, 9)} SOL",
        fee_title=f"{TR.solana__expected_fee}:",
        items=(
            (f"{TR.words__account}:", _format_path(signer_path)),
            (f"{TR.words__blockhash}:", base58.encode(blockhash)),
        ),
    )


async def confirm_transaction(
    signer_path: list[int], blockhash: bytes, fee: int
) -> None:
    await confirm_solana_tx(
        amount="",
        amount_title="",
        fee=f"{format_amount(fee, 9)} SOL",
        fee_title=f"{TR.solana__expected_fee}:",
        items=(
            (f"{TR.words__account}:", _format_path(signer_path)),
            (f"{TR.words__blockhash}:", base58.encode(blockhash)),
        ),
    )
