from typing import TYPE_CHECKING, Optional, Callable, Sequence
from PyQt6.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton, QComboBox, QLineEdit, QHBoxLayout

import electrum_ecc as ecc

from electrum.i18n import _
from electrum.lnutil import MIN_FUNDING_SAT
from electrum.lnworker import hardcoded_trampoline_nodes
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates
from electrum.fee_policy import FeePolicy
from electrum.lntransport import extract_nodeid, ConnStringFormatError

from .util import (WindowModalDialog, Buttons, OkButton, CancelButton,
                   EnterButton, WWLabel, char_width_in_lineedit)
from .amountedit import BTCAmountEdit
from .my_treeview import create_toolbar_with_menu

if TYPE_CHECKING:
    from electrum.transaction import PartialTxInput
    from .main_window import ElectrumWindow


class NewChannelDialog(WindowModalDialog):

    def __init__(
        self,
        window: 'ElectrumWindow',
        *,
        amount_sat: Optional[int] = None,
        min_amount_sat: Optional[int] = None,
        get_coins: Optional[Callable[..., Sequence['PartialTxInput']]] = None,
    ):
        WindowModalDialog.__init__(self, window, _('Open Channel'))
        self.window = window
        self.network = window.network
        self.config = window.config
        self.lnworker = self.window.wallet.lnworker
        self.trampolines = hardcoded_trampoline_nodes()
        self.trampoline_names = list(self.trampolines.keys())
        self.min_amount_sat = min_amount_sat or MIN_FUNDING_SAT
        self.get_coins = get_coins
        vbox = QVBoxLayout(self)
        toolbar, menu = create_toolbar_with_menu(self.config, '')
        menu.addConfig(
            self.config.cv.LIGHTNING_USE_RECOVERABLE_CHANNELS,
            checked=self.lnworker.has_recoverable_channels(),
        ).setEnabled(self.lnworker.can_have_recoverable_channels())
        vbox.addLayout(toolbar)
        msg = _('Choose a remote node and an amount to fund the channel.')
        msg += '\n' + _('Minimum required amount: {}').format(self.window.format_amount_and_units(self.min_amount_sat))
        vbox.addWidget(WWLabel(msg))
        if self.network.channel_db:
            vbox.addWidget(QLabel(_('Enter Remote Node ID or connection string or invoice')))
            self.remote_nodeid = QLineEdit()
            self.remote_nodeid.setMinimumWidth(700)
            self.remote_nodeid.textChanged.connect(self.maybe_enable_ok_button)
            self.suggest_button = QPushButton(self, text=_('Suggest Peer'))
            self.suggest_button.clicked.connect(self.on_suggest)
        else:
            self.trampoline_combo = QComboBox()
            self.trampoline_combo.addItems(self.trampoline_names)
            # index 1 is "Electrum trampoline" on mainnet, this defaults to -1 if 1 is not available
            self.trampoline_combo.setCurrentIndex(1)
            self.trampoline_combo.currentIndexChanged.connect(self.maybe_enable_ok_button)
        self.amount_e = BTCAmountEdit(self.window.get_decimal_point)
        self.amount_e.setAmount(amount_sat)
        self.amount_e.textChanged.connect(self.maybe_enable_ok_button)

        btn_width = 10 * char_width_in_lineedit()
        self.min_button = EnterButton(_("Min"), self.spend_min)
        self.min_button.setEnabled(bool(self.min_amount_sat))
        self.min_button.setFixedWidth(btn_width)
        self.max_button = EnterButton(_("Max"), self.spend_max)
        self.max_button.setFixedWidth(btn_width)
        self.max_button.setCheckable(True)
        self.clear_button = QPushButton(self, text=_('Clear'))
        self.clear_button.clicked.connect(self.on_clear)
        self.clear_button.setFixedWidth(btn_width)
        h = QGridLayout()
        if self.network.channel_db:
            h.addWidget(QLabel(_('Remote Node ID')), 0, 0)
            h.addWidget(self.remote_nodeid, 0, 1, 1, 4)
            h.addWidget(self.suggest_button, 0, 5)
        else:
            h.addWidget(QLabel(_('Remote Node')), 0, 0)
            h.addWidget(self.trampoline_combo, 0, 1, 1, 4)
        h.addWidget(QLabel('Amount'), 2, 0)

        amt_hbox = QHBoxLayout()
        amt_hbox.setContentsMargins(0, 0, 0, 0)
        amt_hbox.addWidget(self.amount_e)
        amt_hbox.addWidget(self.min_button)
        amt_hbox.addWidget(self.max_button)
        amt_hbox.addWidget(self.clear_button)
        amt_hbox.addStretch()
        h.addLayout(amt_hbox, 2, 1, 1, 4)

        vbox.addLayout(h)
        vbox.addStretch()
        self.ok_button = OkButton(self)
        self.ok_button.setDefault(True)
        self.maybe_enable_ok_button()
        vbox.addLayout(Buttons(CancelButton(self), self.ok_button))

    def maybe_enable_ok_button(self):
        enable = True
        if self.network.channel_db:
            try:
                extract_nodeid(str(self.remote_nodeid.text()).strip())
            except ConnStringFormatError:
                enable = False
        else:
            try:
                self.trampoline_names[self.trampoline_combo.currentIndex()]
            except IndexError:
                enable = False
        if not self.amount_e.get_amount():
            enable = False
        self.ok_button.setEnabled(enable)

    def on_suggest(self):
        self.network.start_gossip()
        nodeid = (self.lnworker.suggest_peer() or b"").hex()
        if not nodeid:
            self.remote_nodeid.setText("")
            self.remote_nodeid.setPlaceholderText(
                _("Couldn't find suitable peer yet, try again later.")
            )
        else:
            self.remote_nodeid.setText(nodeid)
        self.remote_nodeid.repaint()  # macOS hack for #6269

    def on_clear(self):
        self.amount_e.setText('')
        self.amount_e.setFrozen(False)
        self.amount_e.repaint()  # macOS hack for #6269
        if self.network.channel_db:
            self.remote_nodeid.setText('')
            self.remote_nodeid.repaint()  # macOS hack for #6269
        self.max_button.setChecked(False)
        self.max_button.repaint()  # macOS hack for #6269

    def spend_min(self):
        self.max_button.setChecked(False)
        self.amount_e.setFrozen(False)
        self.amount_e.setAmount(self.min_amount_sat)

    def spend_max(self):
        self.amount_e.setFrozen(self.max_button.isChecked())
        if not self.max_button.isChecked():
            return
        dummy_nodeid = ecc.GENERATOR.get_public_key_bytes(compressed=True)
        make_tx = self.window.mktx_for_open_channel(funding_sat='!', node_id=dummy_nodeid, get_coins=self.get_coins)
        try:
            tx = make_tx(FeePolicy(self.config.FEE_POLICY))
        except (NotEnoughFunds, NoDynamicFeeEstimates) as e:
            self.max_button.setChecked(False)
            self.amount_e.setFrozen(False)
            self.window.show_error(str(e))
            return
        amount = tx.output_value()
        amount = min(amount, self.config.LIGHTNING_MAX_FUNDING_SAT)
        self.amount_e.setAmount(amount)

    def run(self):
        if not self.exec():
            return
        if self.max_button.isChecked() and self.amount_e.get_amount() < self.config.LIGHTNING_MAX_FUNDING_SAT:
            # if 'max' enabled and amount is strictly less than max allowed,
            # that means we have fewer coins than max allowed, and hence we can
            # spend all coins
            funding_sat = '!'
        else:
            funding_sat = self.amount_e.get_amount()
        if not funding_sat:
            return
        if funding_sat != '!':
            if self.min_amount_sat and funding_sat < self.min_amount_sat:
                self.window.show_error(_('Amount too low'))
                return
        if self.network.channel_db:
            connect_str = str(self.remote_nodeid.text()).strip()
        else:
            name = self.trampoline_names[self.trampoline_combo.currentIndex()]
            connect_str = str(self.trampolines[name])
        if not connect_str:
            return
        self.window.open_channel(connect_str, funding_sat, get_coins=self.get_coins)
        return True
