from storage.sd_salt import SD_CARD_HOT_SWAPPABLE
from trezor import io, wire
from trezor.ui.layouts import confirm_action, show_error_and_raise


class SdCardUnavailable(wire.ProcessError):
    pass


async def _confirm_retry_wrong_card(ctx: wire.GenericContext) -> None:
    if SD_CARD_HOT_SWAPPABLE:
        await confirm_action(
            ctx,
            "warning_wrong_sd",
            "SD card protection",
            "Wrong SD card.",
            "Please insert the correct SD card for this device.",
            verb="Retry",
            verb_cancel="Abort",
            exc=SdCardUnavailable("Wrong SD card."),
        )
    else:
        await show_error_and_raise(
            ctx,
            "warning_wrong_sd",
            "Please unplug the device and insert the correct SD card.",
            "Wrong SD card.",
            exc=SdCardUnavailable("Wrong SD card."),
        )


async def _confirm_retry_insert_card(ctx: wire.GenericContext) -> None:
    if SD_CARD_HOT_SWAPPABLE:
        await confirm_action(
            ctx,
            "warning_no_sd",
            "SD card protection",
            "SD card required.",
            "Please insert your SD card.",
            verb="Retry",
            verb_cancel="Abort",
            exc=SdCardUnavailable("SD card required."),
        )
    else:
        await show_error_and_raise(
            ctx,
            "warning_no_sd",
            "Please unplug the device and insert your SD card.",
            "SD card required.",
            exc=SdCardUnavailable("SD card required."),
        )


async def _confirm_format_card(ctx: wire.GenericContext) -> None:
    # Format card? yes/no
    await confirm_action(
        ctx,
        "warning_format_sd",
        "SD card error",
        "Unknown filesystem.",
        "Use a different card or format the SD card to the FAT32 filesystem.",
        verb="Format",
        verb_cancel="Cancel",
        exc=SdCardUnavailable("SD card not formatted."),
    )

    # Confirm formatting
    await confirm_action(
        ctx,
        "confirm_format_sd",
        "Format SD card",
        "All data on the SD card will be lost.",
        "Do you really want to format the SD card?",
        reverse=True,
        verb="Format SD card",
        hold=True,
        exc=SdCardUnavailable("SD card not formatted."),
    )


async def confirm_retry_sd(
    ctx: wire.GenericContext,
    exc: wire.ProcessError = SdCardUnavailable("Error accessing SD card."),
) -> None:
    await confirm_action(
        ctx,
        "warning_sd_retry",
        "SD card problem",
        None,
        "There was a problem accessing the SD card.",
        verb="Retry",
        verb_cancel="Abort",
        exc=exc,
    )


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.
    """
    from trezor import sdcard

    while not sdcard.is_present():
        await _confirm_retry_insert_card(ctx)

    if not ensure_filesystem:
        return
    fatfs = io.fatfs  # local_cache_attribute
    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

            await _confirm_format_card(ctx)

            # 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)
            await confirm_retry_sd(ctx)


async def request_sd_salt(
    ctx: wire.GenericContext = wire.DUMMY_CONTEXT,
) -> bytearray | None:
    import storage.sd_salt as storage_sd_salt

    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, io.fatfs.NoFilesystem):
            await _confirm_retry_wrong_card(ctx)
        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.
            await confirm_retry_sd(ctx)
