// SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
// SPDX-FileCopyrightText: 2016-2017 tezeb <tezeb+github@outoftheblue.pl>
// SPDX-FileCopyrightText: 2018 Lukas Vogel <lukedirtwalker@gmail.com>
// SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
// SPDX-FileCopyrightText: 2019 Maciej S. Szmigiero <mail@maciej.szmigiero.name>
// SPDX-FileCopyrightText: 2023 g10 Code GmbH
// SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
// SPDX-License-Identifier: GPL-3.0-or-later

#include "passwordviewerwidget.h"
#include "clipboardhelper.h"
#include "config.h"
#include "job/filedecryptjob.h"
#include "passentry.h"
#include "qpushbuttonfactory.h"
#include "qrcodepopup.h"
#include "util.h"
#include "widgets/kplaceholderwidget.h"

#include <QAction>
#include <QApplication>
#include <QDialog>
#include <QFormLayout>
#include <QLabel>
#include <QMainWindow>
#include <QPushButton>
#include <QStackedLayout>
#include <QStatusBar>
#include <QStyle>
#include <QTextBrowser>
#include <QVBoxLayout>

#include <KLocalizedString>
#include <KPasswordLineEdit>
#include <KTitleWidget>

namespace
{
std::unique_ptr<QLabel> createTextBrowserForNotes()
{
    auto textBrowser = std::make_unique<QLabel>();
    textBrowser->setWordWrap(!Config::self()->noLineWrapping());
    textBrowser->setOpenExternalLinks(true);
    textBrowser->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
    textBrowser->setContextMenuPolicy(Qt::DefaultContextMenu);
    textBrowser->setContentsMargins(0, 8, 0, 0);
    return textBrowser;
}
}

PasswordViewerWidget::PasswordViewerWidget(ClipboardHelper *clipboardHelper, QWidget *parent)
    : QWidget(parent)
    , m_placeholderWidget(new KPlaceholderWidget(this))
    , m_titleWidget(new KTitleWidget(this))
    , m_clipboardHelper(clipboardHelper)
    , m_contentLayout(new QFormLayout)
    , m_copyPasswordNameButton(new QPushButton(QIcon::fromTheme(QStringLiteral("edit-copy-symbolic")), {}, this))
{
    auto layout = new QVBoxLayout(this);
    layout->setContentsMargins({});
    layout->setSpacing(0);

    m_stackedLayout = new QStackedLayout(layout);

    auto retryAction = new QAction(QIcon::fromTheme(QStringLiteral("document-decrypt")), i18nc("@action:button", "Retry decryption"));
    connect(retryAction, &QAction::triggered, this, [this]() {
        decryptFile();
    });
    m_placeholderWidget->addAction(retryAction);

    auto scrollArea = new Kleo::AdjustingScrollArea(this);
    m_stackedLayout->addWidget(scrollArea);
    m_stackedLayout->addWidget(m_placeholderWidget);

    auto widget = new QWidget;
    scrollArea->setWidget(widget);
    auto hbox = new QVBoxLayout(widget);

    auto mainLayout = new QVBoxLayout;
    hbox->addLayout(mainLayout);
    mainLayout->setContentsMargins(style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
                                   style()->pixelMetric(QStyle::PM_LayoutTopMargin),
                                   style()->pixelMetric(QStyle::PM_LayoutRightMargin),
                                   style()->pixelMetric(QStyle::PM_LayoutBottomMargin));

    // Setup ui
    m_copyPasswordNameButton->setAccessibleName(i18nc("@action:button", "Copy to clipboard"));
    m_copyPasswordNameButton->setToolTip(i18nc("@info:tooltip", "Copy title to clipboard"));
    m_copyPasswordNameButton->setVisible(false);

    auto titleLayout = new QHBoxLayout;
    titleLayout->addWidget(m_titleWidget);
    titleLayout->insertStretch(1);
    titleLayout->addWidget(m_copyPasswordNameButton);

    connect(m_copyPasswordNameButton, &QPushButton::clicked, this, [this]() {
        m_clipboardHelper->copyTextToClipboard(m_titleWidget->text().trimmed());
    });
    mainLayout->addLayout(titleLayout);
    mainLayout->addLayout(m_contentLayout);

    setPanelTimer();
    clearPanelTimer.setSingleShot(true);
    connect(&clearPanelTimer, &QTimer::timeout, this, &PasswordViewerWidget::clear);

    hbox->addStretch();

    auto statusBar = new QStatusBar;
    statusBar->setProperty("_breeze_statusbar_separator", true);
    statusBar->setVisible(false);
    layout->addWidget(statusBar);
    connect(m_clipboardHelper, &ClipboardHelper::showMessage, statusBar, &QStatusBar::showMessage);
    connect(statusBar, &QStatusBar::messageChanged, this, [statusBar](const QString &message) {
        statusBar->setVisible(!message.isEmpty());
    });
}

QString PasswordViewerWidget::filePath() const
{
    return m_filePath;
}

QString PasswordViewerWidget::rawContent() const
{
    return m_rawContent;
}

void PasswordViewerWidget::setFilePath(const QString &entryName, const QString &filePath)
{
    m_entryName = entryName;
    m_filePath = filePath;
    decryptFile();
}

void PasswordViewerWidget::fileDecryptedSlot(KJob *job)
{
    auto decryptJob = qobject_cast<FileDecryptJob *>(job);
    Q_ASSERT(decryptJob);

    if (decryptJob->error() == KJob::NoError) {
        m_stackedLayout->setCurrentIndex(0);
    } else {
        m_stackedLayout->setCurrentIndex(1);
        m_placeholderWidget->setIcon(QStringLiteral("data-error"));
        m_placeholderWidget->setTitle(i18nc("@title", "Failed to Decrypt Password"));
        m_placeholderWidget->setText(decryptJob->errorString());
    }
    setRawContent(decryptJob->content());
}

void PasswordViewerWidget::setRawContent(const QString &rawContent)
{
    m_rawContent = rawContent;
    const PassEntry entry(m_entryName, m_rawContent);
    setPassEntry(entry, m_rawContent);

    Q_EMIT loaded();
}

void PasswordViewerWidget::setPassEntry(const PassEntry &entry, const QString &content)
{
    QString password = entry.password();
    QString output = content;

    m_clipboardHelper->setClippedText(password);

    // first clear the current view:
    clear();

    m_titleWidget->setText(entry.name());
    m_titleWidget->show();
    m_copyPasswordNameButton->show();

    // show what is needed:
    if (!password.isEmpty()) {
        // set the password, it is hidden if needed in addToGridLayout
        addToGridLayout(i18n("Password:"), password);
    }

    const auto namedValues = entry.namedValues();
    for (const auto &nv : namedValues) {
        addToGridLayout(i18nc("Field label", "%1:", nv.name), nv.value);
    }

    output = entry.remainingDataForDisplay();

    if (Config::self()->viewerAutoClearEnabled()) {
        clearPanelTimer.start();
    }

    if (!output.isEmpty()) {
        output = output.toHtmlEscaped();
        output.replace(Util::protocolRegex(), QStringLiteral(R"(<a href="\1">\1</a>)"));
        output.replace(QLatin1Char('\n'), QStringLiteral("<br />"));
        auto textBrowser = createTextBrowserForNotes();
        textBrowser->setText(output);
        m_contentLayout->addRow(new QLabel(i18nc("@label", "Notes:")), textBrowser.release());
    }
}

void PasswordViewerWidget::clearTemplateWidgets()
{
    while (m_contentLayout->count() > 0) {
        m_contentLayout->removeRow(m_contentLayout->rowCount() - 1);
    }
}

void PasswordViewerWidget::addToGridLayout(const QString &field, const QString &value)
{
    QString trimmedField = field.trimmed();
    QString trimmedValue = value.trimmed();

    // Combine the Copy button and the line edit in one widget
    auto rowLayout = new QHBoxLayout();

    if (trimmedField == i18n("Password:")) {
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
        auto *line = new KPasswordLineEdit();
        line->setRevealPasswordMode(KPassword::RevealMode::Always);
        line->setReadOnly(true);
        line->setPassword(trimmedValue);
#else
        auto *line = new QLineEdit();
        line->setText(trimmedValue);
        auto iconOn = QIcon::fromTheme(QStringLiteral("password-show-on"));
        auto iconOff = QIcon::fromTheme(QStringLiteral("password-show-off"));
        auto action = line->addAction(iconOn, QLineEdit::TrailingPosition);
        action->setCheckable(true);
        action->setText(i18n("Toggle password visibility"));
        connect(action, &QAction::triggered, this, [line, action, iconOn, iconOff]() {
            if (line->echoMode() == QLineEdit::Password) {
                line->setEchoMode(QLineEdit::Normal);
                action->setIcon(iconOff);
            } else {
                line->setEchoMode(QLineEdit::Password);
                action->setIcon(iconOn);
            }
        });
#endif
        line->setObjectName(trimmedField);
        line->setContentsMargins(0, 0, 0, 0);
        line->setEchoMode(QLineEdit::Password);
        rowLayout->addWidget(line);
    } else {
        auto *line = new QLabel();
        line->setOpenExternalLinks(true);
        line->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard);
        line->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
        line->setObjectName(trimmedField);
        trimmedValue.replace(Util::protocolRegex(), QStringLiteral(R"(<a href="\1">\1</a>)"));
        line->setText(trimmedValue);
        line->setContentsMargins(0, 0, 0, 0);
        rowLayout->addWidget(line);
    }

    auto fieldName = trimmedField;
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
    fieldName.removeLast(); // remove ':' from the end of the label
#else
    fieldName.remove(fieldName.count() - 1, 1); // remove ':' from the end of the label
#endif

    auto qrButton =
        createPushButton(QIcon::fromTheme(QStringLiteral("view-barcode-qr")), i18n("View '%1' QR Code", fieldName), m_clipboardHelper, [trimmedValue]() {
            const auto topLevelWidgets = QApplication::topLevelWidgets();
            for (const auto widget : topLevelWidgets) {
                if (qobject_cast<QMainWindow *>(widget)) {
                    auto popup = new QrCodePopup(trimmedValue, widget);
                    popup->show();
                    break;
                }
            }
        });
    rowLayout->addWidget(qrButton.release());

    auto fieldLabel =
        createPushButton(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy '%1' to clipboard", fieldName), m_clipboardHelper, [this, trimmedValue] {
            m_clipboardHelper->copyTextToClipboard(trimmedValue);
        });

    rowLayout->addWidget(fieldLabel.release());

    // set into the layout
    m_contentLayout->addRow(trimmedField, rowLayout);
}

void PasswordViewerWidget::clear()
{
    clearTemplateWidgets();
    m_titleWidget->setText(QString{});
    m_copyPasswordNameButton->hide();
}

void PasswordViewerWidget::setPanelTimer()
{
    clearPanelTimer.setInterval(1000 * Config::self()->viewerAutoClearTime());
}

void PasswordViewerWidget::decryptFile()
{
    m_stackedLayout->setCurrentIndex(0);
    auto decryptJob = new FileDecryptJob(m_filePath);
    connect(decryptJob, &FileDecryptJob::result, this, &PasswordViewerWidget::fileDecryptedSlot);
    decryptJob->start();
}
