"""Login dialog shown when a probed TCP AIS endpoint looks secured.

Driven by an endpoint_probe.ProbeResult: pre-fills TLS from the scan, lets the
user choose the auth method (none / username+password / token), and renders the
server-specific login line(s) to send on connect from a template with
{user}/{pass}/{token} placeholders (AIS has no standard login).

Credentials come from QGIS's **encrypted auth store** (QgsAuthManager) via the
native QgsAuthConfigSelect picker — pick or create a config and the secret lives,
encrypted, in QGIS; the plugin only keeps the config *id* (a safe reference). A
session-only manual fallback is offered when no stored credential is chosen
(handy for a quick test; never written to disk). The id + TLS + template + method
are remembered per host:port in QgsSettings so the next connect pre-fills.

result_config() returns {use_tls, tls_insecure, login_lines, authcfg} for the
caller; login_lines already have the secret substituted in.
"""
from qgis.PyQt.QtWidgets import (
    QDialog, QVBoxLayout, QFormLayout, QLabel, QLineEdit, QCheckBox, QComboBox,
    QPlainTextEdit, QDialogButtonBox)
from qgis.PyQt.QtCore import Qt

try:
    from qgis.gui import QgsAuthConfigSelect
except Exception:                              # pragma: no cover
    QgsAuthConfigSelect = None

_METHOD_NONE = "None (open / TLS only)"
_METHOD_USERPASS = "Username + password"
_METHOD_TOKEN = "Token / API key"  # nosec B105

_TPL_USERPASS = "{user}\n{pass}"
_TPL_TOKEN = "{token}"  # nosec B105


def _load_credential(authcfg):
    """Return (username, password) from a QGIS auth config id, decrypting via
    QgsAuthManager (prompts for the master password if needed). ('','') on any
    problem. The secret never leaves this call except as the rendered login."""
    if not authcfg:
        return "", ""
    try:
        from qgis.core import QgsApplication, QgsAuthMethodConfig
        am = QgsApplication.authManager()
        cfg = QgsAuthMethodConfig()
        am.loadAuthenticationConfig(authcfg, cfg, True)   # full=True -> secrets
        return cfg.config("username") or "", cfg.config("password") or ""
    except Exception:                          # noqa: BLE001
        return "", ""


def _settings_key(host, port):
    return "ais_nmea_source/tcpauth/%s_%s" % (host, port)


class ConnectSecurityDialog(QDialog):
    """Collect TLS + login settings for a secured TCP AIS endpoint."""

    def __init__(self, probe_result, parent=None):
        """Build the dialog from a probe result; restore any remembered settings
        for this host:port (id/TLS/template/method — never the secret)."""
        super().__init__(parent)
        self._r = probe_result
        self.setWindowTitle("AIS endpoint security")
        lay = QVBoxLayout(self)

        intro = QLabel("Endpoint <b>%s:%s</b><br>Scan: %s"
                       % (probe_result.host, probe_result.port,
                          probe_result.summary()))
        intro.setTextFormat(Qt.RichText)
        intro.setWordWrap(True)
        lay.addWidget(intro)

        form = QFormLayout()
        self.tls_chk = QCheckBox("Connect over TLS (encrypted)")
        self.tls_chk.setChecked(bool(probe_result.tls))
        form.addRow(self.tls_chk)
        self.insecure_chk = QCheckBox("Allow self-signed / unverified certificate")
        form.addRow(self.insecure_chk)

        self.method = QComboBox()
        self.method.addItems([_METHOD_NONE, _METHOD_USERPASS, _METHOD_TOKEN])
        if probe_result.auth_required:
            self.method.setCurrentText(_METHOD_USERPASS)
        self.method.currentTextChanged.connect(self._on_method)
        form.addRow("Authentication:", self.method)

        # Native encrypted credential picker (QgsAuthManager).
        self._authsel = QgsAuthConfigSelect(self) if QgsAuthConfigSelect else None
        if self._authsel is not None:
            self.auth_row = self._row(form, "Stored credential:", self._authsel)
        else:
            self.auth_row = (QLabel(), QLabel("(QGIS auth store unavailable)"))

        # Session-only manual fallback (used only when no stored credential set).
        self.user_edit = QLineEdit()
        self.pass_edit = QLineEdit(); self.pass_edit.setEchoMode(QLineEdit.Password)
        self.token_edit = QLineEdit(); self.token_edit.setEchoMode(QLineEdit.Password)
        self.user_row = self._row(form, "Username (session only):", self.user_edit)
        self.pass_row = self._row(form, "Password (session only):", self.pass_edit)
        self.token_row = self._row(form, "Token (session only):", self.token_edit)

        self.tpl_edit = QPlainTextEdit()
        self.tpl_edit.setPlaceholderText(
            "Login line(s) sent on connect — use {user} {pass} {token}.")
        self.tpl_edit.setMaximumHeight(70)
        self.tpl_row_label = QLabel("Login line(s):")
        form.addRow(self.tpl_row_label, self.tpl_edit)
        lay.addLayout(form)

        hint = QLabel("Stored credentials are kept in QGIS's encrypted auth "
                      "database; the plugin only remembers the config id. The "
                      "session-only fields are never written to disk. The login "
                      "line format is server-specific.")
        hint.setWordWrap(True)
        hint.setStyleSheet("color:#777;font-size:10pt;")
        lay.addWidget(hint)

        bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        bb.accepted.connect(self._accept)
        bb.rejected.connect(self.reject)
        lay.addWidget(bb)

        self._restore()
        self._on_method(self.method.currentText())

    @staticmethod
    def _row(form, label, widget):
        lbl = QLabel(label)
        form.addRow(lbl, widget)
        return lbl, widget

    def _on_method(self, text):
        """Show only the rows relevant to the chosen method; seed the template."""
        up = (text == _METHOD_USERPASS)
        tok = (text == _METHOD_TOKEN)
        any_auth = up or tok
        self.auth_row[0].setVisible(any_auth)
        self.auth_row[1].setVisible(any_auth)
        for lbl, w in (self.user_row, self.pass_row):
            lbl.setVisible(up); w.setVisible(up)
        self.token_row[0].setVisible(tok); self.token_row[1].setVisible(tok)
        self.tpl_row_label.setVisible(any_auth)
        self.tpl_edit.setVisible(any_auth)
        cur = self.tpl_edit.toPlainText().strip()
        if cur in ("", _TPL_USERPASS.strip(), _TPL_TOKEN.strip()):
            self.tpl_edit.setPlainText(_TPL_USERPASS if up else
                                       _TPL_TOKEN if tok else "")

    # ---- credential resolution ------------------------------------------- #
    def _authcfg(self):
        try:
            return self._authsel.configId() if self._authsel else ""
        except Exception:                      # noqa: BLE001
            return ""

    def _resolve(self, method):
        """(user, secret): from the stored QGIS credential if one is selected,
        else the session-only manual fields."""
        authcfg = self._authcfg()
        if authcfg:
            user, pw = _load_credential(authcfg)
            if method == _METHOD_TOKEN:
                return "", (pw or user)        # token kept in password (or user) field
            return user, pw
        if method == _METHOD_TOKEN:
            return "", self.token_edit.text()
        return self.user_edit.text(), self.pass_edit.text()

    def _login_lines(self):
        method = self.method.currentText()
        if method == _METHOD_NONE:
            return []
        tpl = self.tpl_edit.toPlainText()
        if not tpl.strip():
            return []
        user, secret = self._resolve(method)
        rendered = (tpl.replace("{user}", user)
                       .replace("{pass}", secret)
                       .replace("{token}", secret))
        return [ln for ln in rendered.splitlines() if ln != ""]

    # ---- persistence (non-secret: id / tls / template / method) ---------- #
    def _restore(self):
        try:
            from qgis.core import QgsSettings
            s = QgsSettings()
            base = _settings_key(self._r.host, self._r.port)
            m = s.value(base + "/method", "")
            if m:
                self.method.setCurrentText(m)
            if s.value(base + "/tls", None) is not None:
                self.tls_chk.setChecked(s.value(base + "/tls", False, type=bool))
                self.insecure_chk.setChecked(
                    s.value(base + "/insecure", False, type=bool))
            tpl = s.value(base + "/template", "")
            if tpl:
                self.tpl_edit.setPlainText(tpl)
            authcfg = s.value(base + "/authcfg", "")
            if authcfg and self._authsel is not None:
                self._authsel.setConfigId(authcfg)
        except Exception:                      # noqa: BLE001  # nosec B110
            pass

    def _accept(self):
        try:
            from qgis.core import QgsSettings
            s = QgsSettings()
            base = _settings_key(self._r.host, self._r.port)
            s.setValue(base + "/method", self.method.currentText())
            s.setValue(base + "/tls", self.tls_chk.isChecked())
            s.setValue(base + "/insecure", self.insecure_chk.isChecked())
            s.setValue(base + "/template", self.tpl_edit.toPlainText())
            s.setValue(base + "/authcfg", self._authcfg())   # id only, not the secret
        except Exception:                      # noqa: BLE001  # nosec B110
            pass
        self.accept()

    def result_config(self):
        """The chosen settings for TcpReader (login_lines already rendered)."""
        return {
            "use_tls": self.tls_chk.isChecked(),
            "tls_insecure": self.insecure_chk.isChecked(),
            "login_lines": self._login_lines(),
            "authcfg": self._authcfg(),
        }
