import storage.sd_salt
from storage.sd_salt import SD_CARD_HOT_SWAPPABLE
from trezor import fatfs, sdcard, ui, wire
from trezor.ui.text import Text

from apps.common.confirm import confirm, hold_to_confirm

if False:
    from typing import Optional


class SdCardUnavailable(wire.ProcessError):
    pass


async def _wrong_card_dialog(ctx: wire.GenericContext) -> bool:
    text = Text("SD card protection", ui.ICON_WRONG)
    text.bold("Wrong SD card.")
    text.br_half()
    if SD_CARD_HOT_SWAPPABLE:
        text.normal("Please insert the", "correct SD card for", "this device.")
        btn_confirm = "Retry"  # type: Optional[str]
        btn_cancel = "Abort"
    else:
        text.normal("Please unplug the", "device and insert the", "correct SD card.")
        btn_confirm = None
        btn_cancel = "Close"

    return await confirm(ctx, text, confirm=btn_confirm, cancel=btn_cancel)


async def insert_card_dialog(ctx: wire.GenericContext) -> bool:
    text = Text("SD card protection", ui.ICON_WRONG)
    text.bold("SD card required.")
    text.br_half()
    if SD_CARD_HOT_SWAPPABLE:
        text.normal("Please insert your", "SD card.")
        btn_confirm = "Retry"  # type: Optional[str]
        btn_cancel = "Abort"
    else:
        text.normal("Please unplug the", "device and insert your", "SD card.")
        btn_confirm = None
        btn_cancel = "Close"

    return await confirm(ctx, text, confirm=btn_confirm, cancel=btn_cancel)


async def format_card_dialog(ctx: wire.GenericContext) -> bool:
    # Format card? yes/no
    text = Text("SD card error", ui.ICON_WRONG, ui.RED)
    text.bold("Unknown filesystem.")
    text.br_half()
    text.normal("Use a different card or")
    text.normal("format the SD card to")
    text.normal("the FAT32 filesystem.")
    if not await confirm(ctx, text, confirm="Format", cancel="Cancel"):
        return False

    # Confirm formatting
    text = Text("Format SD card", ui.ICON_WIPE, ui.RED)
    text.normal("Do you really want to", "format the SD card?")
    text.br_half()
    text.bold("All data on the SD card", "will be lost.")
    return await hold_to_confirm(ctx, text, confirm="Format SD card")


async def sd_problem_dialog(ctx: wire.GenericContext) -> bool:
    text = Text("SD card problem", ui.ICON_WRONG, ui.RED)
    text.normal("There was a problem", "accessing the SD card.")
    return await confirm(ctx, text, confirm="Retry", cancel="Abort")


async def ensure_sdcard(
    ctx: wire.GenericContext, ensure_filesystem: bool = True
) -> None:
    """Ensure a SD card is ready for use.

    This function runs the UI flow needed to ask the user to insert a SD card if there
    isn't one.

    If `ensure_filesystem` is True (the default), it also tries to mount the SD card
    filesystem, and allows the user to format the card if a filesystem cannot be
    mounted.
    """
    while not sdcard.is_present():
        if not await insert_card_dialog(ctx):
            raise SdCardUnavailable("SD card required.")

    if not ensure_filesystem:
        return

    while True:
        try:
            try:
                with sdcard.filesystem(mounted=False):
                    fatfs.mount()
            except fatfs.NoFilesystem:
                # card not formatted. proceed out of the except clause
                pass
            else:
                # no error when mounting
                return

            if not await format_card_dialog(ctx):
                raise SdCardUnavailable("SD card not formatted.")

            # Proceed to formatting. Failure is caught by the outside OSError handler
            with sdcard.filesystem(mounted=False):
                fatfs.mkfs()
                fatfs.mount()
                fatfs.setlabel("TREZOR")

            # format and mount succeeded
            return

        except OSError:
            # formatting failed, or generic I/O error (SD card power-on failed)
            if not await sd_problem_dialog(ctx):
                raise SdCardUnavailable("Error accessing SD card.")


async def request_sd_salt(
    ctx: wire.GenericContext = wire.DUMMY_CONTEXT,
) -> Optional[bytearray]:
    if not storage.sd_salt.is_enabled():
        return None

    while True:
        await ensure_sdcard(ctx, ensure_filesystem=False)
        try:
            return storage.sd_salt.load_sd_salt()
        except (storage.sd_salt.WrongSdCard, fatfs.NoFilesystem):
            if not await _wrong_card_dialog(ctx):
                raise SdCardUnavailable("Wrong SD card.")
        except OSError:
            # Generic problem with loading the SD salt (hardware problem, or we could
            # not read the file, or there is a staged salt which cannot be committed).
            # In either case, there is no good way to recover. If the user clicks Retry,
            # we will try again.
            if not await sd_problem_dialog(ctx):
                raise SdCardUnavailable("Error accessing SD card.")
