/***************************************************************************
 *   SPDX-License-Identifier: GPL-2.0-or-later
 *                                                                         *
 *   SPDX-FileCopyrightText: 2004-2025 Thomas Fischer <fischer@unix-ag.uni-kl.de>
 *                                                                         *
 *   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 <https://www.gnu.org/licenses/>. *
 ***************************************************************************/

#include "onlinesearchieeexplore.h"

#include <QNetworkReply>
#include <QUrl>
#include <QUrlQuery>
#include <QXmlStreamReader>
#include <QRegularExpression>

#ifdef HAVE_KF
#include <KLocalizedString>
#else // HAVE_KF
#define i18n(text) QObject::tr(text)
#endif // HAVE_KF

#include <EncoderXML>
#include <FileImporterBibTeX>
#include "internalnetworkaccessmanager.h"
#include "logging_networking.h"

class OnlineSearchIEEEXplore::OnlineSearchIEEEXplorePrivate
{
public:
    static const QUrl apiUrl;

    OnlineSearchIEEEXplorePrivate(OnlineSearchIEEEXplore *)
    {
        // nothing
    }

    QUrl buildQueryUrl(const QMap<QueryKey, QString> &query, int numResults) {
        QUrl queryUrl = apiUrl;
        QUrlQuery q(queryUrl.query());

        /// Free text
        const QStringList freeTextFragments = OnlineSearchAbstract::splitRespectingQuotationMarks(query[QueryKey::FreeText]);
        if (!freeTextFragments.isEmpty())
            q.addQueryItem(QStringLiteral("querytext"), QStringLiteral("\"") + freeTextFragments.join(QStringLiteral("\"+\"")) + QStringLiteral("\""));

        /// Title
        const QStringList title = OnlineSearchAbstract::splitRespectingQuotationMarks(query[QueryKey::Title]);
        if (!title.isEmpty())
            q.addQueryItem(QStringLiteral("article_title"), QStringLiteral("\"") + title.join(QStringLiteral("\"+\"")) + QStringLiteral("\""));

        /// Author
        const QStringList authors = OnlineSearchAbstract::splitRespectingQuotationMarks(query[QueryKey::Author]);
        if (!authors.isEmpty())
            q.addQueryItem(QStringLiteral("author"), QStringLiteral("\"") + authors.join(QStringLiteral("\"+\"")) + QStringLiteral("\""));

        /// Year
        if (!query[QueryKey::Year].isEmpty()) {
            q.addQueryItem(QStringLiteral("start_year"), query[QueryKey::Year]);
            q.addQueryItem(QStringLiteral("end_year"), query[QueryKey::Year]);
        }

        /// Sort order of results: newest publications first
        q.addQueryItem(QStringLiteral("sort_field"), QStringLiteral("publication_year"));
        q.addQueryItem(QStringLiteral("sort_order"), QStringLiteral("desc"));
        /// Request numResults many entries
        q.addQueryItem(QStringLiteral("start_record"), QStringLiteral("1"));
        q.addQueryItem(QStringLiteral("max_records"), QString::number(numResults));

        queryUrl.setQuery(q);

        return queryUrl;
    }

    QVector<QSharedPointer<Entry>> parseIeeeXML(const QByteArray &xmlData, bool *ok) {
        QVector<QSharedPointer<Entry>> result;

        // Using code generated by Python script 'onlinesearch-parser-generator.py' using
        // information from file 'onlinesearchieeexplore-parser.in.cpp'.
        #include "onlinesearch/onlinesearchieeexplore-parser.generated.cpp"

        return result;    }
};

const QUrl OnlineSearchIEEEXplore::OnlineSearchIEEEXplorePrivate::apiUrl(QStringLiteral("https://ieeexploreapi.ieee.org/api/v1/search/articles?format=xml&apikey=") + InternalNetworkAccessManager::reverseObfuscate("\x15\x65\x4b\x2a\x37\x5f\x78\x12\x44\x70\xf8\x8e\x85\xe0\xdb\xae\xb\x7a\x7e\x46\xab\x93\xbc\xc8\xdb\xa8\xa5\xd2\xee\x96\x7e\x7\x37\x54\xa3\xd4\x2b\x5e\x81\xe6\x6f\x17\xb3\xd6\x7b\x1f\x1a\x60"));

OnlineSearchIEEEXplore::OnlineSearchIEEEXplore(QObject *parent)
        : OnlineSearchAbstract(parent), d(new OnlineSearchIEEEXplore::OnlineSearchIEEEXplorePrivate(this))
{
    /// nothing
}

OnlineSearchIEEEXplore::~OnlineSearchIEEEXplore()
{
    delete d;
}

void OnlineSearchIEEEXplore::startSearch(const QMap<QueryKey, QString> &query, int numResults)
{
    m_hasBeenCanceled = false;
    Q_EMIT progress(curStep = 0, numSteps = 1);

    QNetworkRequest request(d->buildQueryUrl(query, numResults));

    // FIXME 'ieeexploreapi.ieee.org' uses a SSL/TLS certificate only valid for 'mashery.com'
    // TODO re-enable certificate validation once problem has been fix (already reported)
    QSslConfiguration requestSslConfig = request.sslConfiguration();
    requestSslConfig.setPeerVerifyMode(QSslSocket::VerifyNone);
    request.setSslConfiguration(requestSslConfig);

    QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request);
    InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply);
    connect(reply, &QNetworkReply::finished, this, &OnlineSearchIEEEXplore::doneFetchingXML);

    refreshBusyProperty();
}

void OnlineSearchIEEEXplore::doneFetchingXML()
{
    Q_EMIT progress(++curStep, numSteps);

    QNetworkReply *reply = static_cast<QNetworkReply *>(sender());

    QUrl redirUrl;
    if (handleErrors(reply, redirUrl)) {
        if (redirUrl.isValid()) {
            /// redirection to another url
            ++numSteps;

            QNetworkRequest request(redirUrl);

            // FIXME 'ieeexploreapi.ieee.org' uses a SSL/TLS certificate only valid for 'mashery.com'
            // TODO re-enable certificate validation once problem has been fix (already reported)
            QSslConfiguration requestSslConfig = request.sslConfiguration();
            requestSslConfig.setPeerVerifyMode(QSslSocket::VerifyNone);
            request.setSslConfiguration(requestSslConfig);

            QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request);
            InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply);
            connect(reply, &QNetworkReply::finished, this, &OnlineSearchIEEEXplore::doneFetchingXML);
        } else {
            const QByteArray xmlCode = reply->readAll();

            bool ok = false;
            const QVector<QSharedPointer<Entry>> entries = d->parseIeeeXML(xmlCode, &ok);

            if (ok) {
                for (const auto &entry : entries)
                    publishEntry(entry);
                stopSearch(resultNoError);
            } else {
                qCWarning(LOG_KBIBTEX_NETWORKING) << "Failed to parse XML data from" << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString();
                stopSearch(resultUnspecifiedError);
            }
        }
    }

    refreshBusyProperty();
}

QString OnlineSearchIEEEXplore::label() const
{
#ifdef HAVE_KF
    return i18n("IEEEXplore");
#else // HAVE_KF
    //= onlinesearch-ieeexplore-label
    return QObject::tr("IEEEXplore");
#endif // HAVE_KF
}

QUrl OnlineSearchIEEEXplore::homepage() const
{
    return QUrl(QStringLiteral("https://ieeexplore.ieee.org/"));
}

QUrl OnlineSearchIEEEXplore::favicon() const
{
    return QUrl(QStringLiteral("https://ieeexplore.ieee.org/assets/img/favicon.ico"));
}

#ifdef BUILD_TESTING
QVector<QSharedPointer<Entry> > OnlineSearchIEEEXplore::parseIeeeXML(const QByteArray &xmlData, bool *ok)
{
    return d->parseIeeeXML(xmlData, ok);
}
#endif // BUILD_TESTING
