// SPDX-FileCopyrightText: 2024 g10 Code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later

#include "setupwidget.h"

#include <libkleo_version.h>
#define KLEO_HAS_CERTIFICATE_CREATION_DIALOG LIBKLEO_VERSION >= QT_VERSION_CHECK(6, 2, 40)

#include "formtextinput.h"
#include "util.h"

#include <KLocalizedString>
#include <KMessageBox>
#include <KTitleWidget>
#include <KUser>

#if KLEO_HAS_CERTIFICATE_CREATION_DIALOG
#include "job/openpgpcertificatecreationjob.h"
#include <Libkleo/CryptoConfig>
#endif

#include <Libkleo/DefaultKeyFilter>
#include <Libkleo/KeyCache>
#include <Libkleo/KeySelectionCombo>

#include <QLabel>
#include <QVBoxLayout>

template<>
bool Kleo::FormTextInput<Kleo::KeySelectionCombo>::hasValue() const
{
    const auto w = widget();
    return w && w->currentIndex() != -1 && !w->currentKey().isNull();
}

template<>
bool Kleo::FormTextInput<Kleo::KeySelectionCombo>::hasAcceptableInput() const
{
    const auto w = widget();
    return hasValue() && !w->currentKey().isInvalid();
}

template<>
void Kleo::FormTextInput<Kleo::KeySelectionCombo>::connectWidget()
{
    const auto w = widget();
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
    QObject::connect(w, &Kleo::KeySelectionCombo::currentIndexChanged, w, [this]() {
#else
    QObject::connect(w, QOverload<int>::of(&QComboBox::currentIndexChanged), w, [this]() {
#endif
        onEditingFinished();
    });
}

template<>
bool Kleo::FormTextInput<KUrlRequester>::hasValue() const
{
    const auto w = widget();
    return w && !w->url().isEmpty();
}

template<>
bool Kleo::FormTextInput<KUrlRequester>::hasAcceptableInput() const
{
    const auto w = widget();
    return w && w->url().isValid() && w->url().isLocalFile();
}

template<>
void Kleo::FormTextInput<KUrlRequester>::connectWidget()
{
    const auto w = widget();
    QObject::connect(w, &KUrlRequester::textEdited, w, [this]() {
        onEditingFinished();
    });
    QObject::connect(w, &KUrlRequester::textChanged, w, [this]() {
        onTextChanged();
    });
}

SetupWidget::SetupWidget(QWidget *parent)
    : QWidget(parent)
{
    auto vbox = new QVBoxLayout(this);
    vbox->addSpacing(0);
    ui.wrapperWidget = new QWidget;
    ui.wrapperWidget->setMaximumWidth(500);
    vbox->addWidget(ui.wrapperWidget);
    vbox->setAlignment(ui.wrapperWidget, Qt::AlignHCenter);

    auto mainLayout = new QVBoxLayout(ui.wrapperWidget);

    auto title = new KTitleWidget();
    title->setText(i18nc("@title:group", "Configuration of the Password Store"));
    mainLayout->addWidget(title);
    mainLayout->addSpacing(8);

    // Location
    ui.locationInput = Kleo::FormTextInput<KUrlRequester>::create(this);
    ui.locationInput->setLabelText(i18nc("@label", "Path:"));
    ui.locationInput->setValueRequiredErrorMessage(i18n("Enter a valid path."));
    ui.locationInput->setInvalidEntryErrorMessage(i18n("Enter a valid path."));
    ui.locationInput->widget()->setMode(KFile::Directory);
    ui.locationInput->widget()->setUrl(QUrl(Util::findPasswordStore()));

    mainLayout->addWidget(ui.locationInput->label());
    mainLayout->addWidget(ui.locationInput->widget());
    mainLayout->addWidget(ui.locationInput->hintLabel());
    mainLayout->addWidget(ui.locationInput->errorLabel());
    mainLayout->addSpacing(8);

    // Key Selection
    ui.keySelectionInput = Kleo::FormTextInput<Kleo::KeySelectionCombo>::create(this);
    ui.keySelectionInput->setLabelText(i18nc("@label", "My Certificate:"));
    ui.keySelectionInput->setValueRequiredErrorMessage(i18n("Select a certificate."));
    ui.keySelectionInput->setInvalidEntryErrorMessage(i18n("Select a certificate."));

    std::shared_ptr<Kleo::DefaultKeyFilter> keyFilter(new Kleo::DefaultKeyFilter);
    keyFilter->setIsOpenPGP(Kleo::DefaultKeyFilter::Set);
    keyFilter->setCanEncrypt(Kleo::DefaultKeyFilter::Set);
    auto combo = ui.keySelectionInput->widget();
    combo->setKeyFilter(keyFilter);
    combo->prependCustomItem(QIcon(), i18n("No certificate"), QStringLiteral("no-key"));

#if KLEO_HAS_CERTIFICATE_CREATION_DIALOG
    QString defaultKey = Kleo::getCryptoConfigStringValue("gpg", "default-key").remove(QLatin1Char('"'));
    if (!defaultKey.isEmpty()) {
        auto keyCache = Kleo::KeyCache::instance();
        auto keyFingerprint = keyCache->findByKeyIDOrFingerprint(defaultKey.toStdString()).primaryFingerprint();
        combo->setDefaultKey(QString::fromLatin1(keyFingerprint));
        combo->refreshKeys();
    }

    combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("password-generate")),
                            i18n("Generate a new OpenPGP certificate"),
                            QStringLiteral("generate-new-key"));

    connect(combo, &Kleo::KeySelectionCombo::customItemSelected, this, [combo, this](const QVariant &type) {
        if (type == QLatin1StringView("no-key")) {
            return;
        } else if (type == QLatin1StringView("generate-new-key")) {
            const KUser user;
            const auto fullName = user.property(KUser::FullName).toString();
            auto job = new OpenPGPCertificateCreationJob(fullName, {}, combo);
            combo->setEnabled(false);
            connect(job, &OpenPGPCertificateCreationJob::finished, this, [combo, job]() {
                combo->setEnabled(true);
                if (job->error() != KJob::NoError) {
                    KMessageBox::error(combo, job->errorText(), i18n("Key Generation Error"));
                    return;
                }
                combo->setDefaultKey(job->fingerprint());
                combo->refreshKeys();
            });
            job->start();
        }
    });
#endif

    mainLayout->addWidget(ui.keySelectionInput->label());
    mainLayout->addWidget(ui.keySelectionInput->hintLabel());
    mainLayout->addWidget(ui.keySelectionInput->errorLabel());
    mainLayout->addWidget(ui.keySelectionInput->widget());
    mainLayout->addSpacing(8);

    auto dialogButtonBox = new QDialogButtonBox;
    ui.saveButton = dialogButtonBox->addButton(i18nc("@action:button", "Save"), QDialogButtonBox::AcceptRole);
    ui.saveButton->setIcon(QIcon::fromTheme(QStringLiteral("document-save-symbolic")));
    mainLayout->addWidget(dialogButtonBox);

    connect(dialogButtonBox, &QDialogButtonBox::accepted, this, &SetupWidget::slotAccepted);

    mainLayout->addStretch();

    connect(ui.keySelectionInput.get(), &Kleo::FormTextInput<Kleo::KeySelectionCombo>::hasErrorChanged, this, &SetupWidget::slotUpdateSaveButtonState);
    connect(ui.locationInput.get(), &Kleo::FormTextInput<KUrlRequester>::hasErrorChanged, this, &SetupWidget::slotUpdateSaveButtonState);
    connect(ui.locationInput->widget(), &KUrlRequester::textChanged, this, &SetupWidget::slotUpdateLocationHint);
    slotUpdateSaveButtonState();
}

SetupWidget::~SetupWidget() = default;

void SetupWidget::slotAccepted()
{
    auto location = ui.locationInput->widget()->url().toLocalFile();
    if (!location.endsWith(u'/')) {
        location += u'/';
    }

    if (!QFileInfo::exists(location)) {
        QDir dir(location);
        const bool ok = dir.mkpath(QStringLiteral("."));

        if (!ok) {
            ui.locationInput->hintLabel()->hide();
            ui.locationInput->errorLabel()->setText(
                i18nc("@info", "Failed to create the password store. Please verify that you have the necessary permissions to create the directory."));
            ui.locationInput->errorLabel()->show();
            return;
        }
    }

    Q_EMIT setupComplete(location, ui.keySelectionInput->widget()->currentKey().keyID());
}

void SetupWidget::slotUpdateSaveButtonState()
{
    ui.saveButton->setEnabled(!ui.locationInput->hasError() && !ui.keySelectionInput->hasError());
}

void SetupWidget::slotUpdateLocationHint()
{
    auto location = ui.locationInput->widget()->url().toLocalFile();
    ui.locationInput->errorLabel()->hide();
    if (!QFileInfo::exists(location)) {
        ui.locationInput->hintLabel()->setText(
            i18nc("@info", "The selected directory does not exists. GnuPG Password Manager will create a new directory automatically."));
        ui.locationInput->hintLabel()->show();
    } else {
        ui.locationInput->hintLabel()->hide();
    }
}
