/*
    SPDX-FileCopyrightText: 2013-2014 Christian Dávid <christian-david@web.de>
    SPDX-FileCopyrightText: 2019 Thomas Baumgart <tbaumgart@kde.org>
    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "konlinejoboutboxview.h"

#include <memory>

#include "ui_konlinejoboutboxview.h"
#include "kmymoneyviewbase_p.h"
#include "konlinetransferform.h"
#include "kmymoneyplugin.h"

#include <KLocalizedString>
#include <KSharedConfig>
#include <KConfigGroup>
#include <QAction>
#include <QMenu>
#include <QTimer>
#include <QModelIndex>
#include <QModelIndexList>
#include <KMessageBox>
#include <KActionCollection>

#include "models.h"
#include "onlinejobmodel.h"
#include "onlinejobadministration.h"
#include "onlinejobtyped.h"
#include "onlinejobmessagesview.h"
#include "onlinejobmessagesmodel.h"
#include "onlinepluginextended.h"

#include "mymoneyaccount.h"
#include "mymoneyfile.h"
#include "menuenums.h"
#include "mymoneyenums.h"
#include "mymoneyexception.h"

#include <QDebug>

class KOnlineJobOutboxViewPrivate : public KMyMoneyViewBasePrivate
{
    Q_DECLARE_PUBLIC(KOnlineJobOutboxView)

public:
    explicit KOnlineJobOutboxViewPrivate(KOnlineJobOutboxView *qq) :
        KMyMoneyViewBasePrivate(),
        q_ptr(qq),
        ui(new Ui::KOnlineJobOutboxView),
        m_needLoad(true),
        m_onlinePlugins(nullptr),
        m_onlineJobModel(nullptr)
    {
    }

    ~KOnlineJobOutboxViewPrivate()
    {
        if (!m_needLoad) {
            // Save column state
            KConfigGroup configGroup = KSharedConfig::openConfig()->group("KOnlineJobOutboxView");
            configGroup.writeEntry("HeaderState", ui->m_onlineJobView->header()->saveState());
        }
    }

    void init()
    {
        Q_Q(KOnlineJobOutboxView);
        m_needLoad = false;
        ui->setupUi(q);

        // Restore column state
        KConfigGroup configGroup = KSharedConfig::openConfig()->group("KOnlineJobOutboxView");
        QByteArray columns;
        columns = configGroup.readEntry("HeaderState", columns);
        ui->m_onlineJobView->header()->restoreState(columns);

        ui->m_onlineJobView->setModel(this->onlineJobsModel());
        q->connect(ui->m_buttonSend, &QAbstractButton::clicked, q, &KOnlineJobOutboxView::slotSendJobs);
        q->connect(ui->m_buttonRemove, &QAbstractButton::clicked, q, &KOnlineJobOutboxView::slotRemoveJob);
        q->connect(ui->m_buttonEdit, &QAbstractButton::clicked, q, static_cast<void (KOnlineJobOutboxView::*)()>(&KOnlineJobOutboxView::slotEditJob));
        q->connect(ui->m_onlineJobView, &QAbstractItemView::doubleClicked, q, static_cast<void (KOnlineJobOutboxView::*)(const QModelIndex &)>(&KOnlineJobOutboxView::slotEditJob));
        q->connect(ui->m_onlineJobView->selectionModel(), &QItemSelectionModel::selectionChanged, q, &KOnlineJobOutboxView::updateButtonState);

        // Set new credit transfer button
        q->connect(pActions[eMenu::Action::AccountCreditTransfer], &QAction::changed, q, &KOnlineJobOutboxView::updateNewCreditTransferButton);
        q->connect(ui->m_buttonNewCreditTransfer, &QAbstractButton::clicked, q, &KOnlineJobOutboxView::slotNewCreditTransfer);
        q->updateNewCreditTransferButton();
    }

    onlineJobModel* onlineJobsModel()
    {
        Q_Q(KOnlineJobOutboxView);
        if (!m_onlineJobModel) {
            m_onlineJobModel = new onlineJobModel(q);
#ifdef KMM_MODELTEST
            /// @todo using the ModelTest feature on the onlineJobModel crashes. Need to fix.
            // new ModelTest(m_onlineJobModel, Models::instance());
#endif
        }
        return m_onlineJobModel;
    }


    void editJob(const QString jobId)
    {
        try {
            const onlineJob constJob = MyMoneyFile::instance()->getOnlineJob(jobId);
            editJob(constJob);
        } catch (const MyMoneyException &) {
            // Prevent a crash in very rare cases
        }
    }

    void editJob(onlineJob job)
    {
        try {
            editJob(onlineJobTyped<creditTransfer>(job));
        } catch (const MyMoneyException &) {
        }
    }

    void editJob(const onlineJobTyped<creditTransfer> job)
    {
        Q_Q(KOnlineJobOutboxView);
        auto transferForm = new kOnlineTransferForm(q);
        transferForm->setOnlineJob(job);
        q->connect(transferForm, &QDialog::rejected, transferForm, &QObject::deleteLater);
        q->connect(transferForm, &kOnlineTransferForm::acceptedForSave, q, &KOnlineJobOutboxView::slotOnlineJobSave);
        q->connect(transferForm, &kOnlineTransferForm::acceptedForSend, q, static_cast<void (KOnlineJobOutboxView::*)(onlineJob)>(&KOnlineJobOutboxView::slotOnlineJobSend));
        q->connect(transferForm, &QDialog::accepted, transferForm, &QObject::deleteLater);
        transferForm->show();
    }

    KOnlineJobOutboxView     *q_ptr;
    std::unique_ptr<Ui::KOnlineJobOutboxView> ui;

    /**
      * This member holds the load state of page
      */
    bool m_needLoad;
    QMap<QString, KMyMoneyPlugin::OnlinePlugin*>* m_onlinePlugins;
    onlineJobModel *m_onlineJobModel;
    MyMoneyAccount m_currentAccount;
};

KOnlineJobOutboxView::KOnlineJobOutboxView(QWidget *parent) :
    KMyMoneyViewBase(*new KOnlineJobOutboxViewPrivate(this), parent)
{
    connect(pActions[eMenu::Action::LogOnlineJob], &QAction::triggered, this, static_cast<void (KOnlineJobOutboxView::*)()>(&KOnlineJobOutboxView::slotOnlineJobLog));
    connect(pActions[eMenu::Action::AccountCreditTransfer], &QAction::triggered, this, &KOnlineJobOutboxView::slotNewCreditTransfer);
}

KOnlineJobOutboxView::~KOnlineJobOutboxView()
{
}

void KOnlineJobOutboxView::updateButtonState() const
{
    Q_D(const KOnlineJobOutboxView);
    const QModelIndexList indexes = d->ui->m_onlineJobView->selectionModel()->selectedRows();
    const int selectedItems = indexes.count();

    // Send button
    //! @todo Enable button if it is useful
    //ui->m_buttonSend->setEnabled(selectedItems > 0);

    // Edit button/action
    bool editable = true;
    QString tooltip;

    if (selectedItems == 1) {
        const onlineJob job = d->ui->m_onlineJobView->model()->data(indexes.first(), onlineJobModel::OnlineJobRole).value<onlineJob>();

        if (!job.isEditable()) {
            editable = false;
            if (job.sendDate().isValid()) {
                /// @todo maybe add a word about unable to edit but able to copy here
                // I don't do it right away since we are in string freeze for 5.0.7
                tooltip = i18n("This job cannot be edited anymore because it was sent already.");
                editable = true;
            } else if (job.isLocked())
                tooltip = i18n("Job is being processed at the moment.");
            else
                Q_ASSERT(false);
        } else if (!onlineJobAdministration::instance()->canEditOnlineJob(job)) {
            editable = false;
            tooltip = i18n("The plugin to edit this job is not available.");
        }
    } else {
        editable = false;
        tooltip = i18n("You need to select a single job for editing.");
    }

    QAction *const onlinejob_edit = pActions[eMenu::Action::EditOnlineJob];
    Q_CHECK_PTR(onlinejob_edit);
    onlinejob_edit->setEnabled(editable);
    onlinejob_edit->setToolTip(tooltip);

    d->ui->m_buttonEdit->setEnabled(editable);
    d->ui->m_buttonEdit->setToolTip(tooltip);

    // Delete button/action
    QAction *const onlinejob_delete = pActions[eMenu::Action::DeleteOnlineJob];
    Q_CHECK_PTR(onlinejob_delete);
    onlinejob_delete->setEnabled(selectedItems > 0);
    d->ui->m_buttonRemove->setEnabled(onlinejob_delete->isEnabled());
}

void KOnlineJobOutboxView::updateNewCreditTransferButton()
{
    Q_D(KOnlineJobOutboxView);
    auto action = pActions[eMenu::Action::AccountCreditTransfer];
    Q_CHECK_PTR(action);
    d->ui->m_buttonNewCreditTransfer->setEnabled(action->isEnabled());
}

void KOnlineJobOutboxView::slotRemoveJob()
{
    Q_D(KOnlineJobOutboxView);
    QAbstractItemModel* model = d->ui->m_onlineJobView->model();
    QModelIndexList indexes = d->ui->m_onlineJobView->selectionModel()->selectedRows();

    while (!indexes.isEmpty()) {
        model->removeRow(indexes.at(0).row());
        indexes = d->ui->m_onlineJobView->selectionModel()->selectedRows();
    }
}

QStringList KOnlineJobOutboxView::selectedOnlineJobs() const
{
    Q_D(const KOnlineJobOutboxView);
    QModelIndexList indexes = d->ui->m_onlineJobView->selectionModel()->selectedRows();

    if (indexes.isEmpty())
        return QStringList();

    QStringList list;
    list.reserve(indexes.count());

    const QAbstractItemModel *const model = d->ui->m_onlineJobView->model();
    Q_FOREACH(const QModelIndex& index, indexes) {
        list.append(model->data(index, onlineJobModel::OnlineJobId).toString());
    }
    return list;
}

void KOnlineJobOutboxView::slotSelectByObject(const MyMoneyObject& obj, eView::Intent intent)
{
    switch(intent) {
    case eView::Intent::UpdateActions:
        updateActions(obj);
        break;

    default:
        break;
    }
}

void KOnlineJobOutboxView::slotSelectByVariant(const QVariantList& variant, eView::Intent intent)
{
    Q_D(KOnlineJobOutboxView);
    switch(intent) {
    case eView::Intent::SetOnlinePlugins:
        if (variant.count() == 1)
            d->m_onlinePlugins = static_cast<QMap<QString, KMyMoneyPlugin::OnlinePlugin*>*>(variant.first().value<void*>());
        break;
    default:
        break;
    }
}

void KOnlineJobOutboxView::slotSendJobs()
{
    Q_D(KOnlineJobOutboxView);
    if (d->ui->m_onlineJobView->selectionModel()->hasSelection())
        slotSendSelectedJobs();
    else
        slotSendAllSendableJobs();
}

void KOnlineJobOutboxView::slotSendAllSendableJobs()
{
    QList<onlineJob> validJobs;
    foreach (const onlineJob& job, MyMoneyFile::instance()->onlineJobList()) {
        if (job.isValid() && job.isEditable())
            validJobs.append(job);
    }
    qDebug() << "I shall send " << validJobs.count() << "/" << MyMoneyFile::instance()->onlineJobList().count() << " onlineJobs";
    if (!validJobs.isEmpty())
        slotOnlineJobSend(validJobs);
//    emit sendJobs(validJobs);
}

void KOnlineJobOutboxView::slotSendSelectedJobs()
{
    Q_D(KOnlineJobOutboxView);
    QModelIndexList indexes = d->ui->m_onlineJobView->selectionModel()->selectedRows();
    if (indexes.isEmpty())
        return;

    // Valid jobs to send
    QList<onlineJob> validJobs;
    validJobs.reserve(indexes.count());

    // Get valid jobs
    const QAbstractItemModel *const model = d->ui->m_onlineJobView->model();
    foreach (const QModelIndex& index, indexes) {
        onlineJob job = model->data(index, onlineJobModel::OnlineJobRole).value<onlineJob>();
        if (job.isValid() && job.isEditable())
            validJobs.append(job);
    }

    // Abort if not all jobs can be sent
    if (validJobs.count() != indexes.count()) {
        KMessageBox::information(this, i18nc("The user selected credit transfers to send. But they cannot be sent.",
                                             "Cannot send selection"),
                                 i18n("Not all selected credit transfers can be sent because some of them are invalid or were already sent."));
        return;
    }

    slotOnlineJobSend(validJobs);
//  emit sendJobs(validJobs);
}

void KOnlineJobOutboxView::slotEditJob()
{
    Q_D(KOnlineJobOutboxView);
    QModelIndexList indexes = d->ui->m_onlineJobView->selectionModel()->selectedIndexes();
    if (!indexes.isEmpty()) {
        QString jobId = d->ui->m_onlineJobView->model()->data(indexes.first(), onlineJobModel::OnlineJobId).toString();
        Q_ASSERT(!jobId.isEmpty());
        d->editJob(jobId);
//    emit editJob(jobId);
    }
}

void KOnlineJobOutboxView::slotEditJob(const QModelIndex &index)
{
    if (!pActions[eMenu::Action::EditOnlineJob]->isEnabled())
        return;

    Q_D(KOnlineJobOutboxView);
    auto jobId = d->ui->m_onlineJobView->model()->data(index, onlineJobModel::OnlineJobId).toString();
    d->editJob(jobId);
//  emit editJob(jobId);
}

void KOnlineJobOutboxView::contextMenuEvent(QContextMenuEvent*)
{
    if (!pActions[eMenu::Action::EditOnlineJob]->isEnabled())
        return;

    Q_D(KOnlineJobOutboxView);
    QModelIndexList indexes = d->ui->m_onlineJobView->selectionModel()->selectedIndexes();
    if (!indexes.isEmpty()) {
//    onlineJob job = d->ui->m_onlineJobView->model()->data(indexes.first(), onlineJobModel::OnlineJobRole).value<onlineJob>();
        pMenus[eMenu::Menu::OnlineJob]->exec(QCursor::pos());
    }
}

/**
 * Do not know why this is needed, but all other views in KMyMoney have it.
 */
void KOnlineJobOutboxView::showEvent(QShowEvent* event)
{
    Q_D(KOnlineJobOutboxView);
    if (d->m_needLoad)
        d->init();

    emit customActionRequested(View::OnlineJobOutbox, eView::Action::AboutToShow);
    // don't forget base class implementation
    QWidget::showEvent(event);
}

void KOnlineJobOutboxView::executeCustomAction(eView::Action action)
{
    Q_D(KOnlineJobOutboxView);
    switch(action) {
    case eView::Action::SetDefaultFocus:
        QTimer::singleShot(0, d->ui->m_onlineJobView, SLOT(setFocus()));
        break;

    case eView::Action::InitializeAfterFileOpen:
        d->onlineJobsModel()->load();
        break;

    case eView::Action::CleanupBeforeFileClose:
        d->onlineJobsModel()->unload();
        break;

    default:
        break;
    }
}

void KOnlineJobOutboxView::updateActions(const MyMoneyObject& obj)
{
    Q_D(KOnlineJobOutboxView);
    if (typeid(obj) != typeid(MyMoneyAccount) &&
            (obj.id().isEmpty() && d->m_currentAccount.id().isEmpty())) // do not disable actions that were already disabled)))
        return;

    const auto& acc = static_cast<const MyMoneyAccount&>(obj);
    d->m_currentAccount = acc;
}

void KOnlineJobOutboxView::slotOnlineJobSave(onlineJob job)
{
    MyMoneyFileTransaction fileTransaction;
    if (job.id().isEmpty())
        MyMoneyFile::instance()->addOnlineJob(job);
    else
        MyMoneyFile::instance()->modifyOnlineJob(job);
    fileTransaction.commit();
}

/** @todo when onlineJob queue is used, continue here */
void KOnlineJobOutboxView::slotOnlineJobSend(onlineJob job)
{
    MyMoneyFileTransaction fileTransaction;
    if (job.id().isEmpty())
        MyMoneyFile::instance()->addOnlineJob(job);
    else
        MyMoneyFile::instance()->modifyOnlineJob(job);
    fileTransaction.commit();

    QList<onlineJob> jobList;
    jobList.append(job);
    slotOnlineJobSend(jobList);
}

void KOnlineJobOutboxView::slotOnlineJobSend(QList<onlineJob> jobs)
{
    Q_D(KOnlineJobOutboxView);
    MyMoneyFile *const kmmFile = MyMoneyFile::instance();
    QMultiMap<QString, onlineJob> jobsByPlugin;

    // Sort jobs by online plugin & lock them
    foreach (onlineJob job, jobs) {
        Q_ASSERT(!job.id().isEmpty());
        // find the provider
        const MyMoneyAccount originAcc = job.responsibleMyMoneyAccount();
        job.setLock();
        job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Debug, "KMyMoneyApp::slotOnlineJobSend", "Added to queue for plugin '" + originAcc.onlineBankingSettings().value("provider").toLower() + '\''));
        MyMoneyFileTransaction fileTransaction;
        kmmFile->modifyOnlineJob(job);
        fileTransaction.commit();
        jobsByPlugin.insert(originAcc.onlineBankingSettings().value("provider").toLower(), job);
    }

    // Send onlineJobs to plugins
    QList<QString> usedPlugins = jobsByPlugin.keys();
    std::sort(usedPlugins.begin(), usedPlugins.end());
    const QList<QString>::iterator newEnd = std::unique(usedPlugins.begin(), usedPlugins.end());
    usedPlugins.erase(newEnd, usedPlugins.end());

    foreach (const QString& pluginKey, usedPlugins) {
        QMap<QString, KMyMoneyPlugin::OnlinePlugin*>::const_iterator it_p = d->m_onlinePlugins->constFind(pluginKey);

        if (it_p != d->m_onlinePlugins->constEnd()) {
            // plugin found, call it
            KMyMoneyPlugin::OnlinePluginExtended *pluginExt = dynamic_cast< KMyMoneyPlugin::OnlinePluginExtended* >(*it_p);
            if (pluginExt == 0) {
                qWarning("Job given for plugin which is not an extended plugin");
                continue;
            }
            //! @fixme remove debug message
            qDebug() << "Sending " << jobsByPlugin.count(pluginKey) << " job(s) to online plugin " << pluginKey;
            QList<onlineJob> jobsToExecute = jobsByPlugin.values(pluginKey);
            QList<onlineJob> executedJobs = jobsToExecute;
            pluginExt->sendOnlineJob(executedJobs);

            // Save possible changes of the online job and remove lock
            MyMoneyFileTransaction fileTransaction;
            foreach (onlineJob job, executedJobs) {
                fileTransaction.restart();
                job.setLock(false);
                kmmFile->modifyOnlineJob(job);
                fileTransaction.commit();
            }

            if (Q_UNLIKELY(executedJobs.size() != jobsToExecute.size())) {
                // OnlinePlugin did not return all jobs
                qWarning() << "Error saving send online tasks. After restart you should see at minimum all successfully executed jobs marked send. Imperfect plugin: " << pluginExt->objectName();
            }

        } else {
            qWarning() << "Error, got onlineJob for an account without online plugin.";
            /** @FIXME can this actually happen? */
        }
    }
}

void KOnlineJobOutboxView::slotOnlineJobLog()
{
    QStringList jobIds = this->selectedOnlineJobs();
    slotOnlineJobLog(jobIds);
}

void KOnlineJobOutboxView::slotOnlineJobLog(const QStringList& onlineJobIds)
{
    onlineJobMessagesView *const dialog = new onlineJobMessagesView();
    onlineJobMessagesModel *const model = new onlineJobMessagesModel(dialog);
    model->setOnlineJob(MyMoneyFile::instance()->getOnlineJob(onlineJobIds.first()));
    dialog->setModel(model);
    dialog->setAttribute(Qt::WA_DeleteOnClose);
    dialog->show();
    // Note: Objects are not deleted here, Qt's parent-child system has to do that.
}

void KOnlineJobOutboxView::slotNewCreditTransfer()
{
    Q_D(KOnlineJobOutboxView);
    auto *transferForm = new kOnlineTransferForm(this);
    if (!d->m_currentAccount.id().isEmpty()) {
        transferForm->setCurrentAccount(d->m_currentAccount.id());
    }
    connect(transferForm, &QDialog::rejected, transferForm, &QObject::deleteLater);
    connect(transferForm, &kOnlineTransferForm::acceptedForSave, this, &KOnlineJobOutboxView::slotOnlineJobSave);
    connect(transferForm, &kOnlineTransferForm::acceptedForSend, this, static_cast<void (KOnlineJobOutboxView::*)(onlineJob)>(&KOnlineJobOutboxView::slotOnlineJobSend));
    connect(transferForm, &QDialog::accepted, transferForm, &QObject::deleteLater);
    transferForm->show();
}
