/*******************************************************************************
***  This file is part of Dux Rei Nummariae.                                 ***
***                                                                          ***
***  Copyright (C) 2017, 2018, 2019, 2020, 2021                              ***
***  John Schneiderman <Licensing _AT_ Schneiderman _DOT_ me>                ***
***                                                                          ***
***  This program is free software: you can redistribute it and/or modify it ***
***  under the terms of the GNU Affero General Public License as published   ***
***  by the Free Software Foundation, either version 3 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 Affero General Public License for more details.             ***
***                                                                          ***
***  You should have received a copy of the GNU Affero General Public License***
***  along with this program. If not, see <http://www.gnu.org/licenses/>.    ***
*******************************************************************************/
#include "OpenExchangeRatesSource.h"
using ::QByteArray;
using ::QObject;
using ::QString;
using drn::conversion::OpenExchangeRatesSource;

#include <pecunia/Codes.h>
using pecunia::currency::Iso4217Codes;
using pecunia::currency::toStdString;
#include <QEventLoop>
using ::QEventLoop;
#include <QJsonArray>
using ::QJsonArray;
#include <QJsonDocument>
using ::QJsonDocument;
#include <QJsonObject>
using ::QJsonObject;
#include <QJsonParseError>
using ::QJsonParseError;
#include <QNetworkReply>
using ::QNetworkReply;
#include <QObject>
using ::QObject;
#include <QtDebug>
// using qDebug.
#include <QUrl>
using ::QUrl;

#include "ConversionErrors.h"
using drn::conversion::ParsingError;
#include <foundation/Error.h>
using drn::foundation::Error;
#include <network/AccessManager.hpp>
using drn::network::AccessManager;
#include <network/NetworkErrors.h>
using drn::network::ReplyError;


namespace
{

const QString latestRates{"https://openexchangerates.org/api/latest.json"};

}

void OpenExchangeRatesSource::replyFinished()
{
	Q_ASSERT(this->reply_ != nullptr);
	qDebug() << "The reply for the OpenExchange rate source finished.";

	if (this->reply_->error() !=  QNetworkReply::NetworkError::NoError)
		throw ReplyError{
			*this->reply_,
			QObject::tr("Failed to get rates from Open Exchange Rates.")
		};
	this->data_ = this->reply_->readAll();
}

OpenExchangeRatesSource::OpenExchangeRatesSource(
	AccessManager& networkManager,
	const QString& appId,
	QObject* parent
) :
	QObject{parent},
	networkManager_{networkManager},
	appId_{appId},
	reply_{nullptr},
	data_{}
{}

OpenExchangeRatesSource::~OpenExchangeRatesSource() = default;

double OpenExchangeRatesSource::fetch(const Iso4217Codes& from, const Iso4217Codes& to)
{
	this->data_.clear();
	const auto fromStr{QString::fromStdString(toStdString(from))};
	const auto toStr{QString::fromStdString(toStdString(to))};
	qDebug() << "Requesting exchange rate from the OpenExchange from" << fromStr << "to" << toStr;
	QUrl authorisedLatestRates{
		latestRates + "?app_id=" + this->appId_ + "&base=" + fromStr + "&symbols=" + toStr
	};
	this->reply_ = this->networkManager_.send(QNetworkRequest{authorisedLatestRates});
	connect(
		this->reply_.get(),
		&QNetworkReply::finished,
		this,
		&OpenExchangeRatesSource::replyFinished
	);
	QEventLoop waitForReply;
	connect(this->reply_.get(), &QNetworkReply::finished, &waitForReply, &QEventLoop::quit);
	waitForReply.exec();
	QJsonParseError parseError;
	const auto json{QJsonDocument::fromJson(this->data_, &parseError)};

	if (parseError.error != QJsonParseError::NoError)
		throw ParsingError{
			this->data_,
			QObject::tr("Failed to extract JSON when converting from '%1' to '%2', because %3.")
				.arg(fromStr)
				.arg(toStr)
				.arg(parseError.errorString())
		};
	const QJsonObject obj{json.object()};

	const auto base{obj.find("base")};

	if (base == obj.end())
		throw ParsingError{
			this->data_,
			QObject::tr("The currency to convert from, %1, was not retrieved.").arg(fromStr)
		};

	if ( ! base.value().isString())
		throw ParsingError{
			this->data_,
			QObject::tr("The currency to convert from, %1, as sent is not a string.").arg(fromStr)
		};

	if (base.value().toString() != fromStr)
		throw ParsingError{
			this->data_,
			QObject::tr("The currency to convert from, %1, as sent is what was requested, %2.")
				.arg(fromStr)
				.arg(base.value().toString())
		};

	const auto ratesMap{obj.find("rates")};

	if (ratesMap == obj.end())
		throw ParsingError{
			this->data_,
			QObject::tr("The currency to convert from '%1' to '%2' did not retrieve any rates.")
				.arg(fromStr)
				.arg(toStr)
		};

	const auto rates{ratesMap.value().toObject()};
	const auto toRate{rates.find(toStr)};

	if (toRate == obj.end())
		throw ParsingError{
			this->data_,
			QObject::tr(
				"The currency to convert from, %1, as sent does not contain the to currency, %2."
			)
				.arg(fromStr)
				.arg(toStr)
		};

	if (toRate.value().isDouble())
		return toRate.value().toDouble();
	throw ParsingError{
		this->data_,
		QObject::tr(
			"The currency to convert from, %1, as sent the to currency, %2, is not a double."
		)
			.arg(fromStr)
			.arg(toStr)
	};
}

const QString& OpenExchangeRatesSource::applicationId() const
{
	return this->appId_;
}
