/*
    SPDX-FileCopyrightText: 2009 Tony Bloomfield <tonybloom@users.sourceforge.net>
    SPDX-FileCopyrightText: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "kgeneratesqldlg.h"

// ----------------------------------------------------------------------------
// Std Includes

#include <memory>

// ----------------------------------------------------------------------------
// System includes

// ----------------------------------------------------------------------------
// QT Includes

#include <QTextStream>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <KLocalizedString>

// ----------------------------------------------------------------------------
// KDE Includes

#include <KMessageBox>
#include <KUrlRequester>
#include <KHelpClient>
#include <KUser>
#include <QDialogButtonBox>
#include <QPushButton>

// ----------------------------------------------------------------------------
// Project Includes

#include "ui_kgeneratesqldlg.h"

#include "mymoneyexception.h"
#include "mymoneyfile.h"
#include "kguiutils.h"
#include "mymoneydbdriver.h"
#include "mymoneydbdef.h"

class KGenerateSqlDlgPrivate
{
    Q_DISABLE_COPY(KGenerateSqlDlgPrivate)
    Q_DECLARE_PUBLIC(KGenerateSqlDlg)

public:
    explicit KGenerateSqlDlgPrivate(KGenerateSqlDlg *qq) :
        q_ptr(qq),
        ui(new Ui::KGenerateSqlDlg),
        m_createTablesButton(nullptr),
        m_saveSqlButton(nullptr),
        m_sqliteSelected(false)
    {
    }

    ~KGenerateSqlDlgPrivate()
    {
        delete ui;
    }

    void init()
    {
        Q_Q(KGenerateSqlDlg);
        ui->setupUi(q);
        m_createTablesButton = ui->buttonBox->addButton(i18n("Create Tables"), QDialogButtonBox::ButtonRole::AcceptRole);
        m_saveSqlButton = ui->buttonBox->addButton(i18n("Save SQL"), QDialogButtonBox::ButtonRole::ActionRole);
        Q_ASSERT(m_createTablesButton);
        Q_ASSERT(m_saveSqlButton);

        q->connect(ui->buttonBox, &QDialogButtonBox::accepted, q, &QDialog::accept);
        q->connect(ui->buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject);
        initializeForm();
    }

    void initializeForm()
    {
        Q_Q(KGenerateSqlDlg);
        m_requiredFields = nullptr;
        // at this point, we don't know which fields are required, so disable everything but the list
        m_saveSqlButton->setEnabled(false);
        m_createTablesButton->setEnabled(false);
        ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);

        ui->urlSqlite->clear();
        ui->textDbName->clear();
        ui->textHostName->clear();
        ui->textPassword->clear();
        ui->textUserName->clear();
        ui->textSQL->clear();
        ui->urlSqlite->setEnabled(false);
        ui->textDbName->setEnabled(false);
        ui->textHostName->setEnabled(false);
        ui->textPassword->setEnabled(false);
        ui->textUserName->setEnabled(false);
        ui->textSQL->setEnabled(false);

        q->connect(ui->buttonBox->button(QDialogButtonBox::Help), &QPushButton::clicked, q, &KGenerateSqlDlg::slotHelp);
    }

    QString selectedDriver()
    {
        auto drivers = ui->listDrivers->selectedItems();
        if (drivers.count() != 1) {
            return QString();
        }

        return drivers[0]->text().section(' ', 0, 0);
    }

    KGenerateSqlDlg      *q_ptr;
    Ui::KGenerateSqlDlg  *ui;
    QPushButton          *m_createTablesButton;
    QPushButton          *m_saveSqlButton;

    QList<QString> m_supportedDrivers;
    //MyMoneyDbDrivers m_map;
    std::unique_ptr<KMandatoryFieldGroup> m_requiredFields;
    bool m_sqliteSelected;
    QExplicitlySharedDataPointer<MyMoneyDbDriver> m_dbDriver;
    QString m_dbName;
};


KGenerateSqlDlg::KGenerateSqlDlg(QWidget *parent) :
    QDialog(parent),
    d_ptr(new KGenerateSqlDlgPrivate(this))
{
    Q_D(KGenerateSqlDlg);
    d->init();
}

KGenerateSqlDlg::~KGenerateSqlDlg()
{
    Q_D(KGenerateSqlDlg);
    delete d;
}

int  KGenerateSqlDlg::exec()
{
    Q_D(KGenerateSqlDlg);
    // list drivers supported by KMM
    auto map = MyMoneyDbDriver::driverMap();
    // list drivers installed on system
    auto list = QSqlDatabase::drivers();
    // join the two
    QStringList::Iterator it = list.begin();
    while (it != list.end()) {
        QString dname = *it;
        if (map.contains(dname)) { // only keep if driver is supported
            dname = dname + " - " + map[dname];
            d->m_supportedDrivers.append(dname);
        }
        ++it;
    }
    if (d->m_supportedDrivers.isEmpty()) {
        // why does KMessageBox not have a standard dialog with Help button?
        if ((KMessageBox::questionTwoActions(this,
                                             i18n("In order to use a database, you need to install some additional software. Click Help for more information"),
                                             i18n("No Qt SQL Drivers"),
                                             KStandardGuiItem::help(),
                                             KStandardGuiItem::cancel()))
            == KMessageBox::PrimaryAction) {
            KHelpClient::invokeHelp("details.database.usage");
        }
        return 1;
    }
    d->ui->listDrivers->clear();
    d->ui->listDrivers->addItems(d->m_supportedDrivers);
    connect(d->ui->listDrivers, &QListWidget::itemSelectionChanged,
            this, &KGenerateSqlDlg::slotdriverSelected);
    return (QDialog::exec());
}

void KGenerateSqlDlg::slotcreateTables()
{
    Q_D(KGenerateSqlDlg);
    if (d->m_sqliteSelected) {
        d->m_dbName = d->ui->urlSqlite->text();
    } else {
        d->m_dbName = d->ui->textDbName->text();
    }
    // check that the database has been pre-created
    {   // all queries etc. must be in a block - see 'remove database' API doc
        Q_ASSERT(!d->selectedDriver().isEmpty());

        QSqlDatabase dbase = QSqlDatabase::addDatabase(d->selectedDriver(), "creation");
        dbase.setHostName(d->ui->textHostName->text());
        dbase.setDatabaseName(d->m_dbName);
        dbase.setUserName(d->ui->textUserName->text());
        dbase.setPassword(d->ui->textPassword->text());
        if (!dbase.open()) {
            KMessageBox::error(this,
                               i18n("Unable to open database.\n"
                                    "You must use an SQL CREATE DATABASE statement before creating the tables.\n")
                              );
            return;
        }
        QSqlQuery q(dbase);
        QString message(i18n("Tables successfully created"));
        QStringList commands = d->ui->textSQL->toPlainText().split('\n');
        QStringList::const_iterator cit;
        for (cit = commands.cbegin(); cit != commands.cend(); ++cit) {
            if (!(*cit).isEmpty()) {
                //qDebug() << "exec" << *cit;
                q.prepare(*cit);
                if (!q.exec()) {
                    QSqlError e = q.lastError();
                    message = i18n(
                        "Creation failed executing statement"
                        "\nExecuted: %1"
                        "\nError No %2: %3",
                        q.executedQuery(),
                        e.nativeErrorCode(),
                        e.text());
                    break;
                }
            }
        }
        KMessageBox::information(this, message);
    }
    QSqlDatabase::removeDatabase("creation");

    auto okButton = d->ui->buttonBox->button(QDialogButtonBox::Ok);
    Q_ASSERT(okButton);
    okButton->setEnabled(true);
}

void KGenerateSqlDlg::slotsaveSQL()
{
    Q_D(KGenerateSqlDlg);
    auto fileName = QFileDialog::getSaveFileName(
                        this,
                        i18n("Select output file"),
                        QString(),
                        QString());
    if (fileName.isEmpty()) return;
    QFile out(fileName);
    if (!out.open(QIODevice::WriteOnly)) return;
    QTextStream s(&out);
    MyMoneyDbDef db;
    s << d->ui->textSQL->toPlainText();
    out.close();

    auto okButton = d->ui->buttonBox->button(QDialogButtonBox::Ok);
    Q_ASSERT(okButton);
    okButton->setEnabled(true);
}

void KGenerateSqlDlg::slotdriverSelected()
{
    Q_D(KGenerateSqlDlg);
    const auto driverName = d->selectedDriver();
    if (driverName.isEmpty()) {
        d->initializeForm();
        return;
    }

    d->m_dbDriver = MyMoneyDbDriver::create(driverName);
    if (!d->m_dbDriver->isTested()) {
        int rc = KMessageBox::warningContinueCancel(nullptr,
                                                    i18n("Database type %1 has not been fully tested in a KMyMoney environment.\n"
                                                         "Please make sure you have adequate backups of your data.\n"
                                                         "Please report any problems to the developer mailing list at "
                                                         "kmymoney-devel@kde.org",
                                                         driverName),
                                                    QString());
        if (rc == KMessageBox::Cancel) {
            d->ui->listDrivers->clearSelection();
            d->initializeForm();
            return;
        }
    }

    d->m_requiredFields.reset(new KMandatoryFieldGroup(this));
    // currently, only sqlite need an external file
    if (d->m_dbDriver->requiresExternalFile()) {
        d->m_sqliteSelected = true;
        d->ui->urlSqlite->setMode(KFile::Mode::File);
        d->ui->urlSqlite->setEnabled(true);
        d->m_requiredFields->add(d->ui->urlSqlite);

        d->ui->textDbName->setEnabled(false);
        d->ui->textHostName->setEnabled(false);
        d->ui->textUserName->setEnabled(false);
    } else {                         // not sqlite3
        d->m_sqliteSelected = false;
        d->ui->urlSqlite->setEnabled(false);
        d->ui->textDbName->setEnabled(true);
        d->ui->textHostName->setEnabled(true);
        d->ui->textUserName->setEnabled(true);
        d->m_requiredFields->add(d->ui->textDbName);
        d->m_requiredFields->add(d->ui->textHostName);
        d->m_requiredFields->add(d->ui->textUserName);
        d->ui->textDbName->setText("KMyMoney");
        d->ui->textHostName->setText("localhost");
        d->ui->textUserName->setText(QString());
        d->ui->textUserName->setText(KUser().loginName());
        d->ui->textPassword->setText(QString());
    }

    d->ui->textPassword->setEnabled(d->m_dbDriver->isPasswordSupported());
    d->m_requiredFields->setOkButton(d->m_createTablesButton);
    d->ui->textSQL->setEnabled(true);
    // check if we have a storage; if not, create a skeleton one
    // we need a storage for MyMoneyDbDef to generate standard accounts
    MyMoneyDbDef db;
    d->ui->textSQL->setText
    (db.generateSQL(d->m_dbDriver));

    d->m_saveSqlButton->setEnabled(true);
    connect(d->m_saveSqlButton, &QPushButton::clicked, this, &KGenerateSqlDlg::slotsaveSQL);
    connect(d->m_createTablesButton, &QPushButton::clicked, this, &KGenerateSqlDlg::slotcreateTables);
}

void KGenerateSqlDlg::slotHelp()
{
    KHelpClient::invokeHelp("details.database.generatesql");
}
