/* SPDX-FileCopyrightText: 2026 Project Tick
 * SPDX-FileContributor: Project Tick
 * SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-MeshMC-MMCO-Module-Exception-1.0
 *
 *   MeshMC - A Custom Launcher for Minecraft
 *   Copyright (C) 2026 Project Tick
 *
 *   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 3 of the License, or
 *   (at your option) any later version, with the additional permission
 *   described in the MeshMC MMCO Module Exception 1.0.
 *
 *   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/>.
 *
 *   You should have received a copy of the MeshMC MMCO Module Exception 1.0
 *   along with this program.  If not, see <https://projecttick.org/licenses/>.
 */

#include <QNetworkAccessManager>
#include <QNetworkReply>

#include "katabasis/PollServer.h"
#include "JsonResponse.h"

namespace
{
	QMap<QString, QString> toVerificationParams(const QVariantMap& map)
	{
		QMap<QString, QString> params;
		for (QVariantMap::const_iterator i = map.constBegin();
			 i != map.constEnd(); ++i) {
			params[i.key()] = i.value().toString();
		}
		return params;
	}
} // namespace

namespace Katabasis
{

	PollServer::PollServer(QNetworkAccessManager* manager,
						   const QNetworkRequest& request,
						   const QByteArray& payload, int expiresIn,
						   QObject* parent)
		: QObject(parent), manager_(manager), request_(request),
		  payload_(payload), expiresIn_(expiresIn)
	{
		expirationTimer.setTimerType(Qt::VeryCoarseTimer);
		expirationTimer.setInterval(expiresIn * 1000);
		expirationTimer.setSingleShot(true);
		connect(&expirationTimer, &QTimer::timeout, this,
				&PollServer::onExpiration);
		expirationTimer.start();

		pollTimer.setTimerType(Qt::VeryCoarseTimer);
		pollTimer.setInterval(5 * 1000);
		pollTimer.setSingleShot(true);
		connect(&pollTimer, &QTimer::timeout, this, &PollServer::onPollTimeout);
	}

	int PollServer::interval() const
	{
		return pollTimer.interval() / 1000;
	}

	void PollServer::setInterval(int interval)
	{
		pollTimer.setInterval(interval * 1000);
	}

	void PollServer::startPolling()
	{
		if (expirationTimer.isActive()) {
			pollTimer.start();
		}
	}

	void PollServer::onPollTimeout()
	{
		qDebug() << "PollServer::onPollTimeout: retrying";
		QNetworkReply* reply = manager_->post(request_, payload_);
		connect(reply, &QNetworkReply::finished, this,
				&PollServer::onReplyFinished);
	}

	void PollServer::onExpiration()
	{
		pollTimer.stop();
		emit serverClosed(false);
	}

	void PollServer::onReplyFinished()
	{
		QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());

		if (!reply) {
			qDebug() << "PollServer::onReplyFinished: reply is null";
			return;
		}

		QByteArray replyData = reply->readAll();
		QMap<QString, QString> params =
			toVerificationParams(parseJsonResponse(replyData));

		// Dump replyData
		// SENSITIVE DATA in RelWithDebInfo or Debug builds
		// qDebug() << "PollServer::onReplyFinished: replyData\n";
		// qDebug() << QString( replyData );

		if (reply->error() == QNetworkReply::TimeoutError) {
			// rfc8628#section-3.2
			// "On encountering a connection timeout, clients MUST unilaterally
			// reduce their polling frequency before retrying.  The use of an
			// exponential backoff algorithm to achieve this, such as doubling
			// the polling interval on each such connection timeout, is
			// RECOMMENDED."
			setInterval(interval() * 2);
			pollTimer.start();
		} else {
			QString error = params.value("error");
			if (error == "slow_down") {
				// rfc8628#section-3.2
				// "A variant of 'authorization_pending', the authorization
				// request is still pending and polling should continue, but the
				// interval MUST be increased by 5 seconds for this and all
				// subsequent requests."
				setInterval(interval() + 5);
				pollTimer.start();
			} else if (error == "authorization_pending") {
				// keep trying - rfc8628#section-3.2
				// "The authorization request is still pending as the end user
				// hasn't yet completed the user-interaction steps
				// (Section 3.3)."
				pollTimer.start();
			} else {
				expirationTimer.stop();
				emit serverClosed(true);
				// let O2 handle the other cases
				emit verificationReceived(params);
			}
		}
		reply->deleteLater();
	}

} // namespace Katabasis
