#!/usr/bin/env python
#
# Electrum - Lightweight Bitcoin Client
# Copyright (C) 2015 Thomas Voegtlin
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from functools import partial
import os
from typing import TYPE_CHECKING

from PyQt6.QtGui import QPixmap, QMovie, QColor
from PyQt6.QtCore import QObject, pyqtSignal, QSize, Qt
from PyQt6.QtWidgets import (QTextEdit, QVBoxLayout, QLabel, QGridLayout, QHBoxLayout,
                             QRadioButton, QCheckBox, QPushButton, QWidget)

from electrum.i18n import _
from electrum.plugin import hook
from electrum.util import InvalidPassword, ChoiceItem
from electrum.logging import Logger, get_logger
from electrum import keystore

from electrum.gui.qt.util import (WindowModalDialog, WaitingDialog, OkButton, CancelButton, Buttons, icon_path,
                                  internal_plugin_icon_path, WWLabel, CloseButton, ColorScheme,
                                  ChoiceWidget, PasswordLineEdit, char_width_in_lineedit)
from electrum.gui.qt.qrcodewidget import QRCodeWidget
from electrum.gui.qt.amountedit import AmountEdit
from electrum.gui.qt.main_window import StatusBarButton
from electrum.gui.qt.wizard.wallet import (WCCreateSeed, WCConfirmSeed, WCHaveSeed, WCEnterExt, WCConfirmExt,
                                           WalletWizardComponent)
from electrum.gui.qt.util import read_QIcon_from_bytes

from .common_qt import TrustedcoinPluginQObject
from .trustedcoin import TrustedCoinPlugin, DISCLAIMER

if TYPE_CHECKING:
    from electrum.gui.qt.main_window import ElectrumWindow
    from electrum.wallet import Abstract_Wallet
    from electrum.gui.qt.wizard.wallet import QENewWalletWizard


class TOS(QTextEdit):
    tos_signal = pyqtSignal()
    error_signal = pyqtSignal(object)


class HandlerTwoFactor(QObject, Logger):

    def __init__(self, plugin, window):
        QObject.__init__(self)
        self.plugin = plugin
        self.window = window
        Logger.__init__(self)

    def prompt_user_for_otp(self, wallet, tx, on_success, on_failure):
        if not isinstance(wallet, self.plugin.wallet_class):
            return
        if wallet.can_sign_without_server():
            return
        if not wallet.keystores['x3'].can_sign(tx, ignore_watching_only=True):
            self.logger.info("twofactor: xpub3 not needed")
            return
        window = self.window.top_level_window()
        auth_code = self.plugin.auth_dialog(window)
        WaitingDialog(parent=window,
                      message=_('Waiting for TrustedCoin server to sign transaction...'),
                      task=lambda: wallet.on_otp(tx, auth_code),
                      on_success=lambda *args: on_success(tx),
                      on_error=on_failure)


class Plugin(TrustedCoinPlugin):

    def __init__(self, parent, config, name):
        super().__init__(parent, config, name)

    @hook
    def load_wallet(self, wallet: 'Abstract_Wallet', window: 'ElectrumWindow'):
        if not isinstance(wallet, self.wallet_class):
            return
        wallet.handler_2fa = HandlerTwoFactor(self, window)
        if wallet.can_sign_without_server():
            msg = ' '.join([
                _('This wallet was restored from seed, and it contains two master private keys.'),
                _('Therefore, two-factor authentication is disabled.')
            ])
            action = lambda: window.show_message(msg)
            icon = read_QIcon_from_bytes(self.read_file("trustedcoin-status-disabled.png"))
        else:
            action = partial(self.settings_dialog, window)
            icon = read_QIcon_from_bytes(self.read_file("trustedcoin-status.png"))
        sb = window.statusBar()
        button = StatusBarButton(icon, _("TrustedCoin"), action, sb.height())
        sb.addPermanentWidget(button)
        self.start_request_thread(window.wallet)

    def auth_dialog(self, window):
        d = WindowModalDialog(window, _("Authorization"))
        vbox = QVBoxLayout(d)
        pw = AmountEdit(None, is_int=True)
        msg = _('Please enter your Google Authenticator code')
        vbox.addWidget(QLabel(msg))
        grid = QGridLayout()
        grid.setSpacing(8)
        grid.addWidget(QLabel(_('Code')), 1, 0)
        grid.addWidget(pw, 1, 1)
        vbox.addLayout(grid)
        msg = _('If you have lost your second factor, you need to restore your wallet from seed in order to request a new code.')
        label = QLabel(msg)
        label.setWordWrap(1)
        vbox.addWidget(label)
        vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
        if not d.exec():
            return
        return pw.get_amount()

    def prompt_user_for_otp(self, wallet, tx, on_success, on_failure):
        wallet.handler_2fa.prompt_user_for_otp(wallet, tx, on_success, on_failure)

    def waiting_dialog_for_billing_info(self, window, *, on_finished=None):
        def task():
            return self.request_billing_info(window.wallet, suppress_connection_error=False)

        def on_error(exc_info):
            e = exc_info[1]
            window.show_error("{header}\n{exc}\n\n{tor}"
                              .format(header=_('Error getting TrustedCoin account info.'),
                                      exc=repr(e),
                                      tor=_('If you keep experiencing network problems, try using a Tor proxy.')))
        return WaitingDialog(parent=window,
                             message=_('Requesting account info from TrustedCoin server...'),
                             task=task,
                             on_success=on_finished,
                             on_error=on_error)

    @hook
    def abort_send(self, window):
        wallet = window.wallet
        if not isinstance(wallet, self.wallet_class):
            return
        if wallet.can_sign_without_server():
            return
        if wallet.billing_info is None:
            self.waiting_dialog_for_billing_info(window)
            return True
        return False

    def settings_dialog(self, window):
        self.waiting_dialog_for_billing_info(window,
                                             on_finished=partial(self.show_settings_dialog, window))

    def icon_path(self, name):
        return internal_plugin_icon_path(self.name, name)

    def show_settings_dialog(self, window, success):
        if not success:
            window.show_message(_('Server not reachable.'))
            return

        wallet = window.wallet
        d = WindowModalDialog(window, _("TrustedCoin Information"))
        d.setMinimumSize(500, 200)
        vbox = QVBoxLayout(d)
        hbox = QHBoxLayout()

        logo = QLabel()
        logo.setPixmap(QPixmap(self.icon_path("trustedcoin-status.png")))
        msg = _('This wallet is protected by TrustedCoin\'s two-factor authentication.') + '<br/>'\
              + _("For more information, visit") + " <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>"
        label = QLabel(msg)
        label.setOpenExternalLinks(1)

        hbox.addStretch(10)
        hbox.addWidget(logo)
        hbox.addStretch(10)
        hbox.addWidget(label)
        hbox.addStretch(10)

        vbox.addLayout(hbox)
        vbox.addStretch(10)

        msg = _('TrustedCoin charges a small fee to co-sign transactions. The fee depends on how many prepaid transactions you buy. An extra output is added to your transaction every time you run out of prepaid transactions.') + '<br/>'
        label = QLabel(msg)
        label.setWordWrap(1)
        vbox.addWidget(label)

        vbox.addStretch(10)
        grid = QGridLayout()
        vbox.addLayout(grid)

        price_per_tx = wallet.price_per_tx
        n_prepay = wallet.num_prepay()
        i = 0
        for k, v in sorted(price_per_tx.items()):
            if k == 1:
                continue
            grid.addWidget(QLabel("Pay every %d transactions:"%k), i, 0)
            grid.addWidget(QLabel(window.format_amount(v/k) + ' ' + window.base_unit() + "/tx"), i, 1)
            b = QRadioButton()
            b.setChecked(k == n_prepay)

            def on_click(b, k):
                self.config.PLUGIN_TRUSTEDCOIN_NUM_PREPAY = k
            b.clicked.connect(partial(on_click, k=k))
            grid.addWidget(b, i, 2)
            i += 1

        n = wallet.billing_info.get('tx_remaining', 0)
        grid.addWidget(QLabel(_("Your wallet has {} prepaid transactions.").format(n)), i, 0)
        vbox.addLayout(Buttons(CloseButton(d)))
        d.exec()

    @hook
    def init_wallet_wizard(self, wizard: 'QENewWalletWizard'):
        wizard.trustedcoin_qhelper = TrustedcoinPluginQObject(self, wizard, None)
        self.extend_wizard(wizard)
        if wizard.start_viewstate and wizard.start_viewstate.view.startswith('trustedcoin_'):
            wizard.start_viewstate.params.update({'icon': self.icon_path('trustedcoin-wizard.png')})

    def extend_wizard(self, wizard: 'QENewWalletWizard'):
        super().extend_wizard(wizard)
        views = {
            'trustedcoin_start': {
                'gui': WCDisclaimer,
                'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
            },
            'trustedcoin_choose_seed': {
                'gui': WCChooseSeed,
                'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
            },
            'trustedcoin_create_seed': {
                'gui': WCCreateSeed,
                'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
            },
            'trustedcoin_create_ext': {
                'gui': WCEnterExt,
                'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
            },
            'trustedcoin_confirm_seed': {
                'gui': WCConfirmSeed,
                'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
            },
            'trustedcoin_confirm_ext': {
                'gui': WCConfirmExt,
                'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
            },
            'trustedcoin_have_seed': {
                'gui': WCHaveSeed,
                'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
            },
            'trustedcoin_have_ext': {
                'gui': WCEnterExt,
                'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
            },
            'trustedcoin_keep_disable': {
                'gui': WCKeepDisable,
                'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
            },
            'trustedcoin_tos': {
                'gui': WCTerms,
                'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
            },
            'trustedcoin_keystore_unlock': {
                'gui': WCKeystorePassword,
                'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
            },
            'trustedcoin_show_confirm_otp': {
                'gui': WCShowConfirmOTP,
                'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
            }
        }
        wizard.navmap_merge(views)

        # insert page offering choice to go online or continue on another system
        ext_online = {
            'trustedcoin_continue_online': {
                'gui': WCContinueOnline,
                'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
                'next': lambda d: 'trustedcoin_tos' if d['trustedcoin_go_online'] else 'wallet_password',
                'accept': self.on_continue_online,
                'last': lambda d: not d['trustedcoin_go_online'] and wizard.is_single_password()
            },
            'trustedcoin_confirm_seed': {
                'next': lambda d: 'trustedcoin_confirm_ext' if wizard.wants_ext(d) else 'trustedcoin_continue_online'
            },
            'trustedcoin_confirm_ext': {
                'next': 'trustedcoin_continue_online',
            },
            'trustedcoin_keep_disable': {
                'next': lambda d: 'trustedcoin_continue_online' if d['trustedcoin_keepordisable'] != 'disable'
                else 'wallet_password',
            }
        }
        wizard.navmap_merge(ext_online)

    def on_continue_online(self, wizard_data):
        if not wizard_data['trustedcoin_go_online']:
            self.logger.debug('Staying offline, create keystores here')
            xprv1, xpub1, xprv2, xpub2, xpub3, short_id = self.create_keys(wizard_data)
            k1 = keystore.from_xprv(xprv1)
            k2 = keystore.from_xpub(xpub2)

            wizard_data['x1'] = k1.dump()
            wizard_data['x2'] = k2.dump()


class WCDisclaimer(WalletWizardComponent):
    def __init__(self, parent, wizard):
        WalletWizardComponent.__init__(self, parent, wizard, title=_('Disclaimer'))

        self.layout().addWidget(WWLabel('\n\n'.join(DISCLAIMER)))
        self.layout().addStretch(1)

        self._valid = True

    def apply(self):
        pass


class WCChooseSeed(WalletWizardComponent):
    def __init__(self, parent, wizard):
        WalletWizardComponent.__init__(self, parent, wizard, title=_('Create or restore'))
        message = _('Do you want to create a new seed, or restore a wallet using an existing seed?')
        choices = [
            ChoiceItem(key='createseed', label=_('Create a new seed')),
            ChoiceItem(key='haveseed', label=_('I already have a seed')),
        ]

        self.choice_w = ChoiceWidget(message=message, choices=choices)
        self.layout().addWidget(self.choice_w)
        self.layout().addStretch(1)

        self._valid = True

    def apply(self):
        self.wizard_data['keystore_type'] = self.choice_w.selected_key


class WCTerms(WalletWizardComponent):
    def __init__(self, parent, wizard):
        WalletWizardComponent.__init__(self, parent, wizard, title=_('Terms and conditions'))
        self._has_tos = False
        self.tos_e = TOS()
        self.tos_e.setReadOnly(True)
        self.layout().addWidget(self.tos_e)

    def on_ready(self):
        self.fetch_terms_and_conditions()

    def fetch_terms_and_conditions(self):
        self.wizard.trustedcoin_qhelper.busyChanged.connect(self.on_busy_changed)
        self.wizard.trustedcoin_qhelper.termsAndConditionsRetrieved.connect(self.on_terms_retrieved)
        self.wizard.trustedcoin_qhelper.termsAndConditionsError.connect(self.on_terms_error)
        self.wizard.trustedcoin_qhelper.fetchTermsAndConditions()

    def on_busy_changed(self):
        self.busy = self.wizard.trustedcoin_qhelper.busy

    def on_terms_retrieved(self, tos: str) -> None:
        self._has_tos = True
        self.tos_e.setText(tos)
        self.validate()

    def on_terms_error(self, error: str) -> None:
        self.error = error

    def validate(self):
        self.valid = self._has_tos

    def apply(self):
        pass


class WCShowConfirmOTP(WalletWizardComponent):
    _logger = get_logger(__name__)

    def __init__(self, parent, wizard):
        WalletWizardComponent.__init__(self, parent, wizard, title=_('Authenticator secret'))
        self._otp_verified = False
        self._is_online_continuation = False

        self.new_otp = QWidget()
        new_otp_layout = QVBoxLayout()
        scanlabel = WWLabel(_('Enter or scan into authenticator app. Then authenticate below'))
        new_otp_layout.addWidget(scanlabel)
        self.qr = QRCodeWidget('')
        new_otp_layout.addWidget(self.qr)
        self.secretlabel = WWLabel()
        new_otp_layout.addWidget(self.secretlabel)
        self.new_otp.setLayout(new_otp_layout)

        self.exist_otp = QWidget()
        exist_otp_layout = QVBoxLayout()
        knownlabel = WWLabel(_('This wallet is already registered with TrustedCoin.'))
        exist_otp_layout.addWidget(knownlabel)
        self.knownsecretlabel = WWLabel(_('If you still have your OTP secret, then authenticate below to finalize wallet creation'))
        exist_otp_layout.addWidget(self.knownsecretlabel)
        self.exist_otp.setLayout(exist_otp_layout)

        self.authlabelnew = WWLabel(_('Then, enter your Google Authenticator code:'))
        self.authlabelexist = WWLabel(_('Google Authenticator code:'))

        self.spinner = QMovie(icon_path('spinner.gif'))
        self.spinner.setScaledSize(QSize(24, 24))
        self.spinner.setBackgroundColor(QColor('black'))
        self.spinner_l = QLabel()
        self.spinner_l.setMargin(5)
        self.spinner_l.setVisible(False)
        self.spinner_l.setMovie(self.spinner)

        self.otp_status_l = QLabel()
        self.otp_status_l.setAlignment(Qt.AlignmentFlag.AlignHCenter)
        self.otp_status_l.setVisible(False)

        self.resetlabel = WWLabel(_('If you have lost your OTP secret, click the button below to request a new secret from the server.'))
        self.button = QPushButton('Request OTP secret')
        self.button.clicked.connect(self.on_request_otp)

        hbox = QHBoxLayout()
        hbox.addWidget(self.authlabelnew)
        hbox.addWidget(self.authlabelexist)
        hbox.addStretch(1)
        hbox.addWidget(self.spinner_l)
        self.otp_e = AmountEdit(None, is_int=True)
        self.otp_e.setFocus()
        self.otp_e.setMaximumWidth(150)
        self.otp_e.textEdited.connect(self.on_otp_edited)
        hbox.addWidget(self.otp_e)

        self.layout().addWidget(self.new_otp)
        self.layout().addWidget(self.exist_otp)
        self.layout().addLayout(hbox)
        self.layout().addWidget(self.otp_status_l)
        self.layout().addWidget(self.resetlabel)
        self.layout().addWidget(self.button)
        self.layout().addStretch(1)

    def on_ready(self):
        self.wizard.trustedcoin_qhelper.busyChanged.connect(self.on_busy_changed)
        self.wizard.trustedcoin_qhelper.remoteKeyError.connect(self.on_remote_key_error)
        self.wizard.trustedcoin_qhelper.otpSuccess.connect(self.on_otp_success)
        self.wizard.trustedcoin_qhelper.otpError.connect(self.on_otp_error)
        self.wizard.trustedcoin_qhelper.remoteKeyError.connect(self.on_remote_key_error)

        # set higher minHeight so the qr code and the input field are shown without scrolling
        prev_height = self.wizard.height()
        prev_min_height = self.wizard.minimumHeight()
        def restore_prev_height():
            self.wizard.setMinimumHeight(prev_min_height)
            self.wizard.resize(self.wizard.width(), prev_height)
            self.wizard.next_button.clicked.disconnect(restore_prev_height)
            self.wizard.back_button.clicked.disconnect(restore_prev_height)
        self.wizard.setMinimumHeight(530)
        self.wizard.next_button.clicked.connect(restore_prev_height)
        self.wizard.back_button.clicked.connect(restore_prev_height)

        self._is_online_continuation = 'seed' not in self.wizard_data
        if self._is_online_continuation:
            self.knownsecretlabel.setText(_('Authenticate below to finalize wallet creation'))

        self.wizard.trustedcoin_qhelper.createKeystore()

    def update(self):
        is_new = bool(self.wizard.trustedcoin_qhelper.remoteKeyState != 'wallet_known')
        self.new_otp.setVisible(is_new)
        self.exist_otp.setVisible(not is_new)
        self.authlabelnew.setVisible(is_new)
        self.authlabelexist.setVisible(not is_new)
        self.authlabelexist.setEnabled(not self._otp_verified)
        self.otp_e.setEnabled(not self._otp_verified)
        self.resetlabel.setVisible(not is_new and not self._otp_verified and not self._is_online_continuation)
        self.button.setVisible(not is_new and not self._otp_verified and not self._is_online_continuation)

        if self.wizard.trustedcoin_qhelper.otpSecret:
            self.secretlabel.setText(self.wizard.trustedcoin_qhelper.otpSecret)
            uri = 'otpauth://totp/Electrum 2FA %s?secret=%s&digits=6' % (
                os.path.basename(self.wizard_data['wallet_name']), self.wizard.trustedcoin_qhelper.otpSecret)
            self.qr.setData(uri)

    def on_busy_changed(self):
        if not self.wizard.trustedcoin_qhelper._verifyingOtp:
            self.busy = self.wizard.trustedcoin_qhelper.busy
            if not self.busy:
                self.update()

    def on_remote_key_error(self, text):
        self._logger.error(text)
        self.error = text

    def on_request_otp(self):
        self.otp_status_l.setVisible(False)
        self.wizard.trustedcoin_qhelper.resetOtpSecret()
        self.update()

    def on_otp_success(self):
        self._otp_verified = True
        self.otp_status_l.setText('Valid!')
        self.otp_status_l.setVisible(True)
        self.otp_status_l.setStyleSheet(ColorScheme.GREEN.as_stylesheet(False))
        self.setEnabled(True)
        self.spinner_l.setVisible(False)
        self.spinner.stop()

        self.valid = True

    def on_otp_error(self, message):
        self.otp_status_l.setText(message)
        self.otp_status_l.setVisible(True)
        self.otp_status_l.setStyleSheet(ColorScheme.RED.as_stylesheet(False))
        self.setEnabled(True)
        self.spinner_l.setVisible(False)
        self.spinner.stop()

    def on_otp_edited(self):
        self.otp_status_l.setVisible(False)
        text = self.otp_e.text()
        if len(text) > 0:
            try:
                otp_int = int(text)
            except ValueError:
                return
        if len(text) == 6:
            # verify otp
            self.wizard.trustedcoin_qhelper.checkOtp(self.wizard.trustedcoin_qhelper.shortId, otp_int)
            self.setEnabled(False)
            self.spinner_l.setVisible(True)
            self.spinner.start()
            self.otp_e.setText('')

    def apply(self):
        pass


class WCKeepDisable(WalletWizardComponent):
    def __init__(self, parent, wizard):
        WalletWizardComponent.__init__(self, parent, wizard, title=_('Restore 2FA wallet'))
        message = ' '.join([
            'You are going to restore a wallet protected with two-factor authentication.',
            'Do you want to keep using two-factor authentication with this wallet,',
            'or do you want to disable it, and have two master private keys in your wallet?'
        ])
        choices = [
            ChoiceItem(key='keep', label=_('Keep')),
            ChoiceItem(key='disable', label=_('Disable')),
        ]
        self.choice_w = ChoiceWidget(message=message, choices=choices)
        self.layout().addWidget(self.choice_w)
        self.layout().addStretch(1)

        self._valid = True

    def apply(self):
        self.wizard_data['trustedcoin_keepordisable'] = self.choice_w.selected_key


class WCContinueOnline(WalletWizardComponent):
    def __init__(self, parent, wizard):
        WalletWizardComponent.__init__(self, parent, wizard, title=_('Continue Online'))
        self.cb_online = QCheckBox(_('Go online to complete wallet creation'))

    def on_ready(self):
        path = os.path.join(os.path.dirname(self.wizard._daemon.config.get_wallet_path()), self.wizard_data['wallet_name'])
        msg = [
            _("Your wallet file is: {}.").format(path),
            _("You need to be online in order to complete the creation of "
              "your wallet. If you want to continue online, keep the checkbox "
              "checked and press Next."),
            _("If you want this system to stay offline "
              "and continue the completion of the wallet on an online system, "
              "uncheck the checkbox and press Finish.")
        ]

        self.layout().addWidget(WWLabel('\n\n'.join(msg)))
        self.layout().addStretch(1)

        self.cb_online.setChecked(True)
        self.cb_online.stateChanged.connect(self.on_updated)
        # self.cb_online.setToolTip(_("Check this box to request a new secret. You will need to retype your seed."))
        self.layout().addWidget(self.cb_online)
        self.layout().setAlignment(self.cb_online, Qt.AlignmentFlag.AlignHCenter)
        self.layout().addStretch(1)

        self._valid = True

    def apply(self):
        self.wizard_data['trustedcoin_go_online'] = self.cb_online.isChecked()


class WCKeystorePassword(WalletWizardComponent):
    def __init__(self, parent, wizard):
        WalletWizardComponent.__init__(self, parent, wizard, title=_('Unlock Keystore'))
        self.layout().addStretch(1)

        hbox2 = QHBoxLayout()
        hbox2.addStretch(1)
        self.pw_e = PasswordLineEdit('', self)
        self.pw_e.setFixedWidth(17 * char_width_in_lineedit())
        self.pw_e.textEdited.connect(self.on_text)
        pw_label = QLabel(_('Password') + ':')
        hbox2.addWidget(pw_label)
        hbox2.addWidget(self.pw_e)
        hbox2.addStretch(1)
        self.layout().addLayout(hbox2)
        self.layout().addStretch(1)

        self.ks = None

    def on_ready(self):
        self.ks = self.wizard_data['xprv1']

    def on_text(self):
        try:
            self.ks.check_password(self.pw_e.text())
        except InvalidPassword:
            self.valid = False
            return
        self.valid = True

    def apply(self):
        if self.valid:
            self.wizard_data['xprv1'] = self.ks.get_master_private_key(self.pw_e.text())
            self.wizard_data['password'] = self.pw_e.text()
