//-----------------------------------------------------------------------------
//  Copyright (C) 2002-2025 Thomas S. Ullrich
//
//  This file is part of "xyscan".
//
//  This file may be used under the terms of the GNU General Public License.
//  This project is free software; you can redistribute it and/or modify it
//  under the terms of the GNU General Public License.
//  
//  Author: Thomas S. Ullrich
//  Last update: Oct 30, 2025
//-----------------------------------------------------------------------------
#include "xyscanUpdater.h"

#include <QMessageBox>
#include <QXmlStreamReader>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QCoreApplication>
#include <QNetworkProxy>
#include <QVersionNumber>
#include <QLoggingCategory>
#include <QSslError>
#include <QUrl>

#include <iostream>
using namespace std;

#define PR(x) cout << #x << " = " << (x) << endl;

Q_LOGGING_CATEGORY(lcUpdater, "xyscan.updater", QtInfoMsg)

xyscanUpdater::xyscanUpdater()
{
    mNextCheckIsSilent = false;
    
    connect(&mManager, &QNetworkAccessManager::finished, this, &xyscanUpdater::downloadFinished);
    
    mCurrentDownload = nullptr;
    
    //
    // Log SSL errors and fail fast (safer than ignoring).
    //
    connect(&mManager, &QNetworkAccessManager::sslErrors,
            this, [](QNetworkReply* reply, const QList<QSslError>& errs){
        for (const auto& e : errs) {
            qCWarning(lcUpdater) << "SSL error:" << e.errorString();
        }
        if (reply) reply->abort();
    });
}

void xyscanUpdater::checkForNewVersion(const QUrl& url)
{
    //
    // Send request to download the file.
    // Allow only one request at a time.
    //
    if (mCurrentDownload == nullptr) {
        QNetworkRequest request(url);
        
        // Identify this app/version to the server.
        request.setHeader(QNetworkRequest::UserAgentHeader,
                          QStringLiteral("xyscan/%1").arg(qApp->applicationVersion()));
        
        // Qt >6.5: built-in transfer timeout (milliseconds).
        request.setTransferTimeout(10000);
        
        // Avoid security downgrades on redirects; cap redirect chain length.
        request.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
                             QNetworkRequest::NoLessSafeRedirectPolicy);
        request.setMaximumRedirectsAllowed(5);
        
        // Fire request.
        mCurrentDownload = mManager.get(request);
        
        // Basic network error logging.
        connect(mCurrentDownload, &QNetworkReply::errorOccurred, this,
                [this](QNetworkReply::NetworkError){
            qCWarning(lcUpdater) << "Network error during update check:"
            << (mCurrentDownload
                ? mCurrentDownload->errorString()
                : QStringLiteral("<null>"));
        });
    }
}

void xyscanUpdater::downloadFinished(QNetworkReply *reply)
{
    // (url currently unused, but preserved in case you want to report it)
    QUrl url = reply->url();
    Q_UNUSED(url);
    
    if (reply->error() != QNetworkReply::NoError) {
        if (!mNextCheckIsSilent) {
            QMessageBox::warning(0, tr("xyscan"),
                                 tr("Cannot check for latest version.\n(%1).\n\n"
                                    "Make sure you are connected to a network and try again.")
                                 .arg(qPrintable(reply->errorString())));
        }
    }
    else {
        //
        // Got the XML file content,
        // decode it and compare version numbers.
        //
        QString latestVersion, wwwLocation;
        
        QByteArray content = reply->readAll();
        QXmlStreamReader reader(content);
        
        while (!reader.atEnd()) {
            reader.readNext();
            if (reader.isStartElement() && reader.attributes().hasAttribute("version")) {
                latestVersion = reader.attributes().value("version").toString();
                reader.readNext();
                if (reader.isCharacters()) {
                    wwwLocation = reader.text().toString();
                }
            }
        }
        
        if (reader.error() && !mNextCheckIsSilent) {
            QMessageBox::warning(0, tr("xyscan"),
                                 tr("Parsing of xml file failed. "
                                    "Cannot check for newer version. Try again later."));
        }
        
        if (!latestVersion.isEmpty() && !reader.error()) {
            // Use QVersionNumber to avoid lexicographic pitfalls (e.g., 4.10 vs 4.9).
            const QVersionNumber latest  = QVersionNumber::fromString(latestVersion);
            const QVersionNumber current = QVersionNumber::fromString(qApp->applicationVersion());
            const int cmp = QVersionNumber::compare(latest, current);
            
            if (cmp > 0) {
                QMessageBox::information(0, "xyscan",
                                         tr("<html>A new version of xyscan (%1) is available.<br>"
                                            "To download go to:<br>"
                                            "<a href=\"%2\">%2</a></html>")
                                         .arg(latestVersion).arg(wwwLocation));
                emit userReminded();
            }
            else if (cmp == 0 && !mNextCheckIsSilent) {
                QMessageBox::information(0, "xyscan",
                                         tr("You are running the latest version of xyscan (%1).")
                                         .arg(qApp->applicationVersion()));
            }
            else {
                if (!mNextCheckIsSilent) {
                    QMessageBox::information(0, "xyscan",
                                             tr("Strange, you are running a newer version (%1) than the latest version "
                                                "available on the web (%2).")
                                             .arg(qApp->applicationVersion()).arg(latestVersion));
                    qCInfo(lcUpdater) << "Server latest" << latest << "older than current" << current;
                }
            }
        }
    }
    
    reply->deleteLater();        // mark for deletion
    mCurrentDownload = nullptr;  // done with this request
    mNextCheckIsSilent = false;
}
