/***************************************************************************
 *   Copyright (C) 2008 by S. MANKOWSKI / G. DE BURE support@mankowski.fr  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>  *
 ***************************************************************************/
/** @file
 * This file is Skrooge plugin for BACKEND import / export.
 *
 * @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgimportpluginbackend.h"

#include <qfile.h>
#include <qfileinfo.h>
#include <qdir.h>
#include <qprocess.h>
#include <qapplication.h>
#include <qstandardpaths.h>
#include <qdiriterator.h>
#include <qtconcurrentmap.h>

#include <klocalizedstring.h>
#include <kaboutdata.h>
#include <kpluginfactory.h>

#include "skgtraces.h"
#include "skgservices.h"
#include "skgbankincludes.h"
#include "skgimportexportmanager.h"

/**
 * This plugin factory.
 */
K_PLUGIN_FACTORY(SKGImportPluginBackendFactory, registerPlugin<SKGImportPluginBackend>();)

SKGImportPluginBackend::SKGImportPluginBackend(QObject* iImporter, const QVariantList& iArg)
    : SKGImportPlugin(iImporter)
{
    SKGTRACEINFUNC(10);
    Q_UNUSED(iArg);

    QString a = QStringLiteral("skrooge/backends");
    const auto dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, a, QStandardPaths::LocateDirectory);
    for (const auto& dir : dirs) {
        QDirIterator it(dir, QStringList() << QStringLiteral("*.backend"));
        while (it.hasNext()) {
            m_listBackends.push_back(QFileInfo(it.next()).baseName().toUpper());
        }
    }
}

SKGImportPluginBackend::~SKGImportPluginBackend()
{}

bool SKGImportPluginBackend::isImportPossible()
{
    SKGTRACEINFUNC(10);
    return (!m_importer ? true : m_listBackends.contains(m_importer->getFileNameExtension()));
}

struct download {
    download(int iNbToDownload, const QString& iDate, const QString& iCmd, const QString& iPwd)
        : m_nbToDownload(iNbToDownload), m_date(iDate), m_cmd(iCmd), m_pwd(iPwd)
    {
    }

    typedef QString result_type;

    QString operator()(const QString& iAccountId)
    {
        QString file = QDir::tempPath() % "/" % iAccountId % ".csv";
        int retry = 0;
        do {
            // Build cmd
            QString cmd = m_cmd;
            cmd = cmd.replace(QStringLiteral("%2"), SKGServices::intToString(m_nbToDownload)).replace(QStringLiteral("%1"), iAccountId).replace(QStringLiteral("%3"), m_pwd).replace(QStringLiteral("%4"), m_date);

            // Execute
            QProcess p;
            SKGTRACEL(10) << "Execute: " << cmd << endl;
            p.setStandardOutputFile(file);
            p.start(cmd);
            if (p.waitForFinished(1000 * 60 * 2) && p.exitCode() == 0) {
                return iAccountId;
            }
            ++retry;
        } while (retry < 6);

        //Remove the temporary file
        QFile::remove(file);

        return QStringLiteral("");
    }

    int m_nbToDownload;
    QString m_date;
    QString m_cmd;
    QString m_pwd;
};

SKGError SKGImportPluginBackend::importFile()
{
    if (!m_importer) {
        return SKGError(ERR_ABORT, i18nc("Error message", "Invalid parameters"));
    }
    SKGError err;
    SKGTRACEINFUNCRC(2, err);

    SKGBEGINPROGRESSTRANSACTION(*m_importer->getDocument(), i18nc("Noun, name of the user action", "Import with %1", "Backend"), err, 3);
    QHash< QString, QString > properties;
    QString bankendName = m_importer->getFileNameExtension().toLower();
    err = SKGServices::readPropertyFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "skrooge/backends/" % bankendName % ".backend"), properties);
    IFOK(err) {
        // Get parameters
        QMap<QString, QString> parameters = this->getImportParameters();
        QString pwd = parameters[QStringLiteral("password")];

        // Get list of accounts
        QStringList backendAccounts;
        QMap<QString, QString> backendAccountsBalance;
        QString csvfile = QDir::tempPath() % "/skrooge_backend.csv";
        QString cmd = properties[QStringLiteral("getaccounts")].replace(QStringLiteral("%3"), pwd);
        QProcess p;
        SKGTRACEL(10) << "Execute: " << cmd << endl;
        p.setStandardOutputFile(csvfile);
        p.start(cmd);
        if (!p.waitForFinished(1000 * 60 * 2) || p.exitCode() != 0) {
            err.setReturnCode(ERR_FAIL).setMessage(i18nc("Error message",  "The following command line failed with code %2:\n'%1'", cmd, p.exitCode()));
        } else {
            QFile file(csvfile);
            if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
                err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Open file '%1' failed", csvfile));
            } else {
                QRegExp reggetaccounts(properties[QStringLiteral("getaccountid")]);
                QRegExp reggetaccountbalance(properties[QStringLiteral("getaccountbalance")]);

                QTextStream stream(&file);
                stream.readLine();  // To avoid header
                QStringList backendAccountsUniqueId;
                while (!stream.atEnd()) {
                    // Read line
                    QString line = stream.readLine().trimmed();

                    // Get account id
                    int pos = reggetaccounts.indexIn(line);
                    if (pos > -1) {
                        QString accountid = reggetaccounts.cap(1);
                        QString uniqueid = SKGServices::splitCSVLine(accountid, QLatin1Char('@')).at(0);

                        if (!backendAccounts.contains(accountid) && !backendAccountsUniqueId.contains(uniqueid)) {
                            backendAccounts.push_back(accountid);
                            backendAccountsUniqueId.push_back(uniqueid);

                            // Get account balance
                            pos = reggetaccountbalance.indexIn(line);
                            if (pos > -1) {
                                backendAccountsBalance[accountid] = reggetaccountbalance.cap(1);
                            } else {
                                backendAccountsBalance[accountid] = '0';
                            }
                        }
                    } else {
                        // This is an error
                        err.setReturnCode(ERR_FAIL).setMessage(line).addError(ERR_FAIL, i18nc("Error message",  "Impossible to find the account id with the regular expression '%1' in line '%2'", properties[QStringLiteral("getaccountid")], line));
                        break;
                    }
                }

                // close file
                file.close();
                file.remove();
            }
        }

        // Download operations
        IFOKDO(err, m_importer->getDocument()->stepForward(1, i18nc("Progress message", "Download operations")))
        IFOK(err) {
            if (backendAccounts.isEmpty()) {
                err.setReturnCode(ERR_FAIL).setMessage(i18nc("Error message",  "Your backend '%1' seems to be not well configure because no account has been found.", bankendName));
            } else {
                // Compute the begin date for download
                QDate lastDownload = SKGServices::stringToTime(m_importer->getDocument()->getParameter("SKG_LAST_" % bankendName.toUpper() % "_IMPORT_DATE")).date();
                QString lastList = m_importer->getDocument()->getParameter("SKG_LAST_" % bankendName.toUpper() % "_IMPORT_LIST");
                QString currentList = backendAccounts.join(QStringLiteral(";"));

                int nbToDownload = 0;
                QString fromDate;
                if (currentList != lastList || !lastDownload.isValid()) {
                    nbToDownload = 99999;
                    fromDate = QStringLiteral("2000-01-01");
                } else {
                    nbToDownload = qMax(lastDownload.daysTo(QDate::currentDate()) * 10, qint64(20));
                    fromDate = SKGServices::dateToSqlString(lastDownload.addDays(-4));
                }

                // Download
                QStringList listDownloadedId;
                {
                    QFuture<QString> f = QtConcurrent::mapped(backendAccounts, download(nbToDownload, fromDate, properties[QStringLiteral("getoperations")], pwd));
                    f.waitForFinished();
                    listDownloadedId = f.results();
                }
                listDownloadedId.removeAll(QStringLiteral(""));

                // Check
                bool checkOK = true;
                int nb = listDownloadedId.count();
                if (100 * nb / backendAccounts.count() < 80) {
                    // Some accounts have not been downloaded
                    if (nb == 0) {
                        err = SKGError(ERR_FAIL, i18nc("Error message", "No accounts downloaded with the following command:\n%1\nCheck your backend installation.", properties[QStringLiteral("getoperations")]));
                    } else {
                        // Warning
                        m_importer->getDocument()->sendMessage(i18nc("Warning message", "Some accounts have not been downloaded"), SKGDocument::Warning);
                    }
                    checkOK = false;
                }

                // import
                IFOKDO(err, m_importer->getDocument()->stepForward(2, i18nc("Progress message", "Import")))
                if (!err && nb) {
                    // import
                    SKGBEGINPROGRESSTRANSACTION(*m_importer->getDocument(), "#INTERNAL#" % i18nc("Noun, name of the user action", "Import one account with %1", "Backend"), err, nb);

                    // Get all messages
                    SKGDocument::SKGMessageList messages;
                    IFOKDO(err, m_importer->getDocument()->getMessages(m_importer->getDocument()->getCurrentTransaction(), messages, true))

                    // Import all files
                    for (int i = 0; !err && i < nb; ++i) {
                        // Import
                        QString file = QDir::tempPath() % "/" % listDownloadedId.at(i) % ".csv";
                        SKGImportExportManager imp1(m_importer->getDocument(), QUrl::fromLocalFile(file));
                        imp1.setAutomaticValidation(m_importer->automaticValidation());
                        imp1.setAutomaticApplyRules(m_importer->automaticApplyRules());
                        imp1.setSinceLastImportDate(m_importer->sinceLastImportDate());
                        imp1.setCodec(m_importer->getCodec());

                        QMap<QString, QString> newParameters = imp1.getImportParameters();
                        newParameters[QStringLiteral("automatic_search_header")] = 'N';
                        newParameters[QStringLiteral("header_position")] = '1';
                        newParameters[QStringLiteral("automatic_search_columns")] = 'N';
                        newParameters[QStringLiteral("columns_positions")] = properties[QStringLiteral("csvcolumns")];
                        newParameters[QStringLiteral("mode_csv_unit")] = 'N';
                        newParameters[QStringLiteral("mode_csv_rule")] = 'N';
                        newParameters[QStringLiteral("balance")] = backendAccountsBalance[listDownloadedId.at(i)];
                        if (i != nb - 1) {
                            newParameters[QStringLiteral("donotfinalize")] = 'Y';
                        }
                        imp1.setImportParameters(newParameters);
                        IFOKDO(err, imp1.importFile())

                        if (!backendAccountsBalance[listDownloadedId.at(i)].isEmpty()) {
                            SKGAccountObject act;
                            IFOKDO(err, imp1.getDefaultAccount(act));

                            // Compute if at least one reconciliation is possible
                            auto targetBalance = SKGServices::stringToDouble(backendAccountsBalance[listDownloadedId.at(i)]);

                            m_importer->addAccountToCheck(act, targetBalance);

                            auto soluces = act.getPossibleReconciliations(targetBalance, false);
                            if (soluces.isEmpty()) {
                                checkOK = false;
                            }
                        }
                        IFOKDO(err, m_importer->getDocument()->stepForward(i + 1))
                    }

                    // Remove all temporary files
                    for (int i = 0; i < nb; ++i) {
                        QString file = QDir::tempPath() % "/" % listDownloadedId.at(i) % ".csv";
                        QFile::remove(file);
                    }

                    // Reset message
                    IFOKDO(err, m_importer->getDocument()->removeMessages(m_importer->getDocument()->getCurrentTransaction()))
                    int nbm = messages.count();
                    for (int j = 0; j < nbm; ++j) {
                        SKGDocument::SKGMessage msg = messages.at(j);
                        m_importer->getDocument()->sendMessage(msg.Text, msg.Type, msg.Action);
                    }

                    if (checkOK) {
                        // Last import is memorized only in case of 100% success
                        IFOKDO(err, m_importer->getDocument()->setParameter("SKG_LAST_" % bankendName.toUpper() % "_IMPORT_DATE", SKGServices::dateToSqlString(QDateTime::currentDateTime())))
                        IFOKDO(err, m_importer->getDocument()->setParameter("SKG_LAST_" % bankendName.toUpper() % "_IMPORT_LIST", currentList))
                    } else  {
                        // Remove last import for next import
                        IFOKDO(err, m_importer->getDocument()->setParameter("SKG_LAST_" % bankendName.toUpper() % "_IMPORT_DATE", QStringLiteral("")))
                        IFOKDO(err, m_importer->getDocument()->setParameter("SKG_LAST_" % bankendName.toUpper() % "_IMPORT_LIST", QStringLiteral("")))
                    }
                }
                IFOKDO(err, m_importer->getDocument()->stepForward(3))
            }
        }
    }

    return err;
}

QString SKGImportPluginBackend::getMimeTypeFilter() const
{
    return QStringLiteral("");
}

#include <skgimportpluginbackend.moc>
